RepoPilotOpen in app →

Baseflow/PhotoView

Implementation of ImageView for Android that supports zooming, by various touch gestures.

Mixed

Stale — last commit 4y ago

weakest axis
Use as dependencyMixed

last commit was 4y ago; no tests detected…

Fork & modifyMixed

no tests detected; no CI workflows detected…

Learn fromHealthy

Documented and popular — useful reference codebase to read through.

Deploy as-isMixed

last commit was 4y ago; no CI workflows detected

  • 20 active contributors
  • Apache-2.0 licensed
  • Stale — last commit 4y ago
Show all 6 evidence items →
  • Concentrated ownership — top contributor handles 60% of recent commits
  • No CI workflows detected
  • No test directory detected
What would change the summary?
  • Use as dependency MixedHealthy if: 1 commit in the last 365 days; add a test suite
  • Fork & modify MixedHealthy if: add a test suite
  • Deploy as-is MixedHealthy if: 1 commit in the last 180 days

Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests

Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.

Embed the "Great to learn from" badge

Paste into your README — live-updates from the latest cached analysis.

RepoPilot: Great to learn from
[![RepoPilot: Great to learn from](https://repopilot.app/api/badge/baseflow/photoview?axis=learn)](https://repopilot.app/r/baseflow/photoview)

Paste at the top of your README.md — renders inline like a shields.io badge.

Preview social card (1200×630)

This card auto-renders when someone shares https://repopilot.app/r/baseflow/photoview on X, Slack, or LinkedIn.

Onboarding doc

Onboarding: Baseflow/PhotoView

Generated by RepoPilot · 2026-05-09 · Source

🤖Agent protocol

If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:

  1. Verify the contract. Run the bash script in Verify before trusting below. If any check returns FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding.
  2. Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
  3. Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/Baseflow/PhotoView shows verifiable citations alongside every claim.

If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.

🎯Verdict

WAIT — Stale — last commit 4y ago

  • 20 active contributors
  • Apache-2.0 licensed
  • ⚠ Stale — last commit 4y ago
  • ⚠ Concentrated ownership — top contributor handles 60% of recent commits
  • ⚠ No CI workflows detected
  • ⚠ No test directory detected

<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>

Verify before trusting

This artifact was generated by RepoPilot at a point in time. Before an agent acts on it, the checks below confirm that the live Baseflow/PhotoView repo on your machine still matches what RepoPilot saw. If any fail, the artifact is stale — regenerate it at repopilot.app/r/Baseflow/PhotoView.

What it runs against: a local clone of Baseflow/PhotoView — the script inspects git remote, the LICENSE file, file paths in the working tree, and git log. Read-only; no mutations.

| # | What we check | Why it matters | |---|---|---| | 1 | You're in Baseflow/PhotoView | Confirms the artifact applies here, not a fork | | 2 | License is still Apache-2.0 | Catches relicense before you depend on it | | 3 | Default branch master exists | Catches branch renames | | 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code | | 5 | Last commit ≤ 1535 days ago | Catches sudden abandonment since generation |

<details> <summary><b>Run all checks</b> — paste this script from inside your clone of <code>Baseflow/PhotoView</code></summary>
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of Baseflow/PhotoView. If you don't
# have one yet, run these first:
#
#   git clone https://github.com/Baseflow/PhotoView.git
#   cd PhotoView
#
# Then paste this script. Every check is read-only — no mutations.

set +e
fail=0
ok()   { echo "ok:   $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }

# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
  echo "FAIL: not inside a git repository. cd into your clone of Baseflow/PhotoView and re-run."
  exit 2
fi

# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "Baseflow/PhotoView(\\.git)?\\b" \\
  && ok "origin remote is Baseflow/PhotoView" \\
  || miss "origin remote is not Baseflow/PhotoView (artifact may be from a fork)"

# 2. License matches what RepoPilot saw
(grep -qiE "^(Apache-2\\.0)" LICENSE 2>/dev/null \\
   || grep -qiE "\"license\"\\s*:\\s*\"Apache-2\\.0\"" package.json 2>/dev/null) \\
  && ok "license is Apache-2.0" \\
  || miss "license drift — was Apache-2.0 at generation time"

# 3. Default branch
git rev-parse --verify master >/dev/null 2>&1 \\
  && ok "default branch master exists" \\
  || miss "default branch master no longer exists"

# 4. Critical files exist
test -f "photoview/src/main/java/com/github/chrisbanes/photoview/PhotoView.java" \\
  && ok "photoview/src/main/java/com/github/chrisbanes/photoview/PhotoView.java" \\
  || miss "missing critical file: photoview/src/main/java/com/github/chrisbanes/photoview/PhotoView.java"
test -f "photoview/src/main/java/com/github/chrisbanes/photoview/PhotoViewAttacher.java" \\
  && ok "photoview/src/main/java/com/github/chrisbanes/photoview/PhotoViewAttacher.java" \\
  || miss "missing critical file: photoview/src/main/java/com/github/chrisbanes/photoview/PhotoViewAttacher.java"
test -f "photoview/src/main/java/com/github/chrisbanes/photoview/CustomGestureDetector.java" \\
  && ok "photoview/src/main/java/com/github/chrisbanes/photoview/CustomGestureDetector.java" \\
  || miss "missing critical file: photoview/src/main/java/com/github/chrisbanes/photoview/CustomGestureDetector.java"
test -f "photoview/src/main/java/com/github/chrisbanes/photoview/OnGestureListener.java" \\
  && ok "photoview/src/main/java/com/github/chrisbanes/photoview/OnGestureListener.java" \\
  || miss "missing critical file: photoview/src/main/java/com/github/chrisbanes/photoview/OnGestureListener.java"
test -f "photoview/build.gradle" \\
  && ok "photoview/build.gradle" \\
  || miss "missing critical file: photoview/build.gradle"

# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 1535 ]; then
  ok "last commit was $days_since_last days ago (artifact saw ~1505d)"
else
  miss "last commit was $days_since_last days ago — artifact may be stale"
fi

echo
if [ "$fail" -eq 0 ]; then
  echo "artifact verified (0 failures) — safe to trust"
else
  echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/Baseflow/PhotoView"
  exit 1
fi

Each check prints ok: or FAIL:. The script exits non-zero if anything failed, so it composes cleanly into agent loops (./verify.sh || regenerate-and-retry).

</details>

TL;DR

PhotoView is an Android ImageView subclass that adds pinch-to-zoom, double-tap zoom, and fling-scroll capabilities with smooth gesture handling. It solves the problem of standard ImageView being unable to handle multi-touch zoom gestures natively, providing out-of-the-box zoomable image viewing with matrix transformations. Simple two-module structure: photoview/src/main/java/com/github/chrisbanes/photoview/ contains core classes (PhotoView, PhotoViewAttacher, CustomGestureDetector, various listener interfaces), and sample/ provides a complete reference implementation. Core logic lives in PhotoViewAttacher.java which handles matrix transformations; PhotoView.java is a thin wrapper around ImageView.

👥Who it's for

Android app developers who need to display images with interactive zoom/pan capabilities—particularly those building image galleries, photo viewers, or document viewers that require smooth multi-touch gesture support without building gesture detection from scratch.

🌱Maturity & risk

Actively maintained production library with clear architecture and sample implementations. Built on stable Android APIs (minSdkVersion 16+, targeting API 29). Includes working samples and a complete implementation in a single focused module. Strong GitHub presence suggests maturity, though commit recency data is not visible in the provided file list.

Low risk—minimal external dependencies (only Android framework). Single-module architecture reduces dependency complexity. Relies on standard Android APIs that are stable across versions. Main risk is single maintainer (chrisbanes) and potential Android framework changes, but the focused scope limits impact surface.

Active areas of work

No specific active work visible in provided file list. The codebase appears stable with established issue templates (.github/ISSUE_TEMPLATE/) for bug reports, feature requests, and regressions, suggesting an open maintenance model. Sample implementations include modern patterns (CoilSampleActivity.kt shows Kotlin usage).

🚀Get running

git clone https://github.com/chrisbanes/PhotoView.git
cd PhotoView
./gradlew build
./gradlew sample:assembleDebug  # Build the sample app

Daily commands:

./gradlew sample:installDebug  # Install sample app to connected device/emulator
adb shell am start -n com.github.chrisbanes.photoview.sample/.MainActivity
# Or in Android Studio: Open sample/ module and click Run

🗺️Map of the codebase

  • photoview/src/main/java/com/github/chrisbanes/photoview/PhotoView.java — Public API entry point that wraps PhotoViewAttacher; all consumer code uses this class to enable zoom functionality on ImageViews.
  • photoview/src/main/java/com/github/chrisbanes/photoview/PhotoViewAttacher.java — Core engine handling all zoom, pan, fling, and gesture logic; understanding this class is essential to modify scaling behavior or gesture handling.
  • photoview/src/main/java/com/github/chrisbanes/photoview/CustomGestureDetector.java — Detects and normalizes all touch gestures (pinch, double-tap, single-tap, long-press, fling); any gesture enhancement or fix requires changes here.
  • photoview/src/main/java/com/github/chrisbanes/photoview/OnGestureListener.java — Primary callback interface that all gesture responses use; defines the contract between gesture detection and scale/pan application.
  • photoview/build.gradle — Module-level build configuration; contributors need to understand min/max SDK targets and any library dependencies added.
  • photoview/src/main/java/com/github/chrisbanes/photoview/Util.java — Utility methods for matrix transformations and validation; critical for understanding how scale and pan matrices are computed.

🛠️How to make changes

Add a new gesture-based interaction (e.g., rotation support)

  1. Define a new listener interface in a new file (e.g., OnRotationChangedListener.java) that callbacks should implement (photoview/src/main/java/com/github/chrisbanes/photoview/OnRotationChangedListener.java)
  2. Add rotation detection logic in CustomGestureDetector.java (onTouchEvent method) to recognize the rotation gesture (photoview/src/main/java/com/github/chrisbanes/photoview/CustomGestureDetector.java)
  3. Emit the gesture callback through the OnGestureListener interface by adding a new method (e.g., onRotation) and calling it from CustomGestureDetector (photoview/src/main/java/com/github/chrisbanes/photoview/OnGestureListener.java)
  4. In PhotoViewAttacher.java, implement the new OnGestureListener method and apply rotation to the transformation matrix using Util matrix methods (photoview/src/main/java/com/github/chrisbanes/photoview/PhotoViewAttacher.java)
  5. Add a public setter method in PhotoViewAttacher to register the OnRotationChangedListener, then expose it in PhotoView.java (photoview/src/main/java/com/github/chrisbanes/photoview/PhotoView.java)
  6. Create a sample activity in the sample module demonstrating the new rotation feature and its listener (sample/src/main/java/com/github/chrisbanes/photoview/sample/RotationSampleActivity.java)

Add a new zoom constraint or animation parameter

  1. Add a new private field and getter/setter in PhotoViewAttacher.java to store the parameter (e.g., minZoomLevel, animationDuration) (photoview/src/main/java/com/github/chrisbanes/photoview/PhotoViewAttacher.java)
  2. Update the zoom limit validation logic in PhotoViewAttacher.java (in scale-related methods) to apply the new constraint (photoview/src/main/java/com/github/chrisbanes/photoview/PhotoViewAttacher.java)
  3. If animation is involved, modify the fling or zoom animation code in PhotoViewAttacher to use the new duration parameter (photoview/src/main/java/com/github/chrisbanes/photoview/PhotoViewAttacher.java)
  4. Expose the setter as a public method in PhotoView.java so consumers can configure the parameter (photoview/src/main/java/com/github/chrisbanes/photoview/PhotoView.java)

Add a new observer event for UI updates (e.g., OnZoomBoundsChanged)

  1. Create a new listener interface file (e.g., OnZoomBoundsChangedListener.java) with callback method signature (photoview/src/main/java/com/github/chrisbanes/photoview/OnZoomBoundsChangedListener.java)
  2. In PhotoViewAttacher.java, add a field to hold registered listeners and a public setter to register them (photoview/src/main/java/com/github/chrisbanes/photoview/PhotoViewAttacher.java)
  3. Identify where zoom bounds change (e.g., in the onScale method or after matrix updates) and emit the listener callback at that point (photoview/src/main/java/com/github/chrisbanes/photoview/PhotoViewAttacher.java)
  4. Add a public convenience method in PhotoView.java that delegates to PhotoViewAttacher to register the listener (photoview/src/main/java/com/github/chrisbanes/photoview/PhotoView.java)

🔧Why these technologies

  • Android Matrix transforms (android.graphics.Matrix) — Native framework support for 2D affine transformations (scale, translate, rotate) applied directly to ImageView; eliminates custom math and GPU overhead
  • GestureDetector / MotionEvent — Standard Android input APIs for detecting multi-touch patterns (pinch, tap, fling); avoids reinventing gesture recognition
  • ValueAnimator / ObjectAnimator — Android animation framework for smooth fling and double-tap zoom animations; integrates with system frame pacing
  • Listener pattern (callbacks) — Decouples gesture engine from UI updates; allows clients to observe zoom/pan state changes without tight coupling

⚖️Trade-offs already made

  • Attach behavior via PhotoViewAttacher composition rather than inheritance
    • Why: Allows PhotoView to be a simple custom view while keeping zoom logic re
    • Consequence: undefined

🪤Traps & gotchas

  1. ViewPager and DrawerLayout will throw exceptions unless wrapped (see HackyDrawerLayout.java pattern—this is a framework limitation, not a bug). 2) Matrix state is cumulative; improper reset can cause transform stacking. 3) Scale bounds must account for image aspect ratio and container size or clipping occurs. 4) Touch event handling bypasses parent ViewGroup dispatch in certain states—requires careful integration in complex layouts.

🏗️Architecture

💡Concepts to learn

  • Canvas Matrix Transformations — PhotoView's entire zoom/pan mechanism relies on manipulating Android's Matrix class to scale and translate the image—understanding matrix math is essential to modifying zoom behavior
  • Multi-touch Gesture Detection — CustomGestureDetector.java implements pinch-zoom and double-tap detection; understanding how Android's GestureDetector works is critical for modifying gesture behavior
  • Fling (Velocity-based Momentum Scrolling) — PhotoView implements fling scrolling via VelocityTracker and Scroller; knowledge of momentum physics is needed to tweak scroll smoothness
  • Touch Event Interception and Dispatch — The HackyDrawerLayout workaround exists because ViewGroups intercept touch events; understanding Android's event dispatch chain is essential for debugging integration issues
  • Scale Bounds and Clipping Constraints — PhotoView enforces min/max zoom levels to prevent over-zoom or under-zoom; understanding how to compute valid scale ranges is critical for custom zoom logic
  • Coordinate Space Transformation — Converting between screen touch coordinates and image matrix-transformed coordinates is non-trivial; Util.java contains helper methods essential for tap-to-zoom precision
  • chrisbanes/accompanist — Jetpack Compose image loading companion library; represents evolution of image handling for modern Android (if PhotoView gets Compose support this would integrate)
  • square/picasso — Image loading library often used with PhotoView to load remote images before displaying in zoom view
  • bumptech/glide — Alternative image loader; same use case as Picasso (feed images to PhotoView)
  • davemorrissey/subsampling-scale-image-view — Direct competitor solving same problem (zoomable ImageView) with different approach—uses tile-based rendering for very large images

🪄PR ideas

To work on one of these in Claude Code or Cursor, paste: Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.

Add instrumented Android tests for PhotoViewAttacher gesture handling

The core PhotoViewAttacher.java class handles complex gesture detection (zoom, pan, fling) through CustomGestureDetector.java and OnGestureListener.java, but there's no test directory visible in the repository structure. Adding instrumented tests would verify touch gesture behavior, scale calculations, and matrix transformations work correctly across Android versions, preventing regressions in this critical functionality.

  • [ ] Create photoview/src/androidTest directory structure
  • [ ] Add instrumented tests for CustomGestureDetector.java (pinch-to-zoom, double-tap, fling gestures)
  • [ ] Add tests for PhotoViewAttacher.java scale/pan calculations and boundary conditions
  • [ ] Add tests for OnMatrixChangedListener.java and OnScaleChangedListener.java callbacks
  • [ ] Configure androidTestImplementation dependencies in photoview/build.gradle

Add GitHub Actions CI/CD workflow for automated testing and APK builds

The repo has a .github/PULL_REQUEST_TEMPLATE.md and issue templates, but no GitHub Actions workflows visible. Adding CI would automatically build the library and sample app on pull requests, run instrumented tests, and verify gradle builds succeed across different SDK versions, catching integration issues early.

  • [ ] Create .github/workflows/build.yml with gradle build steps for photoview module
  • [ ] Add build matrix for minSdkVersion=16 and sdkVersion=29 (from gradle.properties)
  • [ ] Include gradle-wrapper validation to ensure reproducible builds
  • [ ] Add automated testing step using Android emulator or Firebase Test Lab
  • [ ] Configure workflow to run on pull requests and commits to main branch

Document missing OnViewDragListener, OnSingleFlingListener, and custom callback features

The codebase has several listener interfaces (OnViewDragListener.java, OnSingleFlingListener.java, OnPhotoTapListener.java, OnOutsidePhotoTapListener.java) that are not mentioned in the README snippet provided, and the README appears incomplete ('Allows the application to be notified when the dis...'). Adding specific documentation with code examples for these listeners would help contributors understand and properly use all photoview features.

  • [ ] Complete the incomplete README.md Features section and add Features subsection
  • [ ] Add 'Listeners & Callbacks' section documenting all interface listeners in photoview/src/main/java/com/github/chrisbanes/photoview/
  • [ ] Include code examples for OnViewDragListener, OnSingleFlingListener, OnPhotoTapListener, and OnOutsidePhotoTapListener
  • [ ] Document the Compat.java utility and version-specific behavior
  • [ ] Add troubleshooting section referencing sample apps (sample/src/main/java/*Activity.java files)

🌿Good first issues

  • Add unit tests for PhotoViewAttacher.java matrix transformations—the file exists but no test coverage is visible in the file list, making it risky for refactoring
  • Document the gesture coordinate system in Util.java and CustomGestureDetector.java—new contributors struggle with translating touch coordinates to matrix space
  • Add visual integration guide showing how to properly embed PhotoView in RecyclerView/ViewPager (sample exists but no dedicated doc; HackyDrawerLayout pattern should be formalized)

Top contributors

Click to expand

📝Recent commits

Click to expand
  • 565505d — Update README.md (martijn00)
  • bd39f74 — Update build settings (martijn00)
  • 264fe88 — Add github templates (martijn00)
  • a16efab — Merge pull request #765 from guyca/master (martijn00)
  • 608525b — Merge pull request #778 from DDihanov/patch-2 (martijn00)
  • 0fbce10 — Update README.md (DDihanov)
  • 180b4db — Update README.md (DDihanov)
  • d5889b8 — Update README.md (DDihanov)
  • 139a9ff — Translate image during scale (guyca)
  • d2a1173 — Merge pull request #755 from andrew8er/pr/754-include-license-in-pom (Jawnnypoo)

🔒Security observations

The PhotoView codebase has significant security concerns primarily related to outdated build tools and dependencies. The Gradle build plugin (4.1.2), Kotlin (1.3.72), and target SDK (29) are all significantly outdated and lack modern security features. The use of the deprecated JCenter repository creates supply chain risks. Critical upgrades are needed to address build infrastructure vulnerabilities and meet current Android security standards. No injection vulnerabilities or hardcoded secrets were identified in the visible code structure, but

  • High · Outdated Gradle Build Plugin — build.gradle - classpath 'com.android.tools.build:gradle:4.1.2'. The gradle version 4.1.2 is significantly outdated. Current stable version is 7.x+. Outdated build tools may contain security vulnerabilities and lack modern security features. Fix: Update to the latest stable Gradle version (8.0+ recommended). Update Android Gradle Plugin to 7.4.0 or higher.
  • High · Outdated Kotlin Plugin — build.gradle - ext.kotlin_version = '1.3.72'. Kotlin version 1.3.72 is outdated and no longer receives security updates. Current stable version is 1.9.x+. Old versions may contain known vulnerabilities. Fix: Update Kotlin to the latest stable version (1.9.0 or higher). Review and test compatibility with the latest Android Gradle Plugin.
  • High · Deprecated JCenter Repository — build.gradle - repositories { jcenter() }. JCenter repository is deprecated and has been shut down. The build configuration still references it, which may cause build failures or unexpected behavior. This can be exploited as a supply chain attack vector. Fix: Remove all references to jcenter(). Use only mavenCentral() and google() repositories. Update gradle.properties to ensure repositories are properly configured.
  • Medium · Low Target SDK Version — build.gradle - ext { sdkVersion = 29 }. The targetSdkVersion is set to 29, which is significantly outdated. Current requirement is 33+ for Google Play Store. Lower SDK versions miss modern Android security features and may be vulnerable to OS-level exploits. Fix: Update targetSdkVersion to 34 or higher (current Google Play requirement is 33+). Test thoroughly with target SDK changes.
  • Medium · Low Minimum SDK Version — build.gradle - ext { minSdkVersion = 16 }. minSdkVersion is set to 16, which is extremely outdated (released 2011). Supporting such old Android versions increases attack surface and misses critical security patches. Fix: Consider raising minSdkVersion to at least 21-24 to drop support for obsolete Android versions with known security vulnerabilities.
  • Medium · Outdated Android Maven Gradle Plugin — build.gradle - classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1'. The android-maven-gradle-plugin version 2.1 is outdated and may not work with modern Gradle versions. This can lead to build inconsistencies and potential security issues in dependency resolution. Fix: Verify if this plugin is still needed. If publishing is required, consider using the modern gradle-maven-publish-plugin or Gradle's built-in publishing capabilities.
  • Low · Missing Security Policy — Repository root - Missing SECURITY.md. No SECURITY.md or security policy file found in the repository. This makes it difficult for security researchers to report vulnerabilities responsibly. Fix: Create a SECURITY.md file documenting how to report security vulnerabilities responsibly. Include contact information and expected response timeframe.
  • Low · No Dependency Version Pinning — README.md - dependencies { implementation 'com.github.chrisbanes:PhotoView:latest.release.here' }. The build.gradle uses 'latest.release.here' placeholder in README, suggesting loose version control. Dynamic version resolution can introduce unexpected breaking changes or vulnerabilities. Fix: Use specific pinned versions for dependencies rather than dynamic ranges. Implement dependency lock files (gradle.lockfile) to ensure reproducible builds.

LLM-derived; treat as a starting point, not a security audit.


Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.

Mixed signals · Baseflow/PhotoView — RepoPilot