Shimmer.NET 1.3.1

dotnet add package Shimmer.NET --version 1.3.1
                    
NuGet\Install-Package Shimmer.NET -Version 1.3.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Shimmer.NET" Version="1.3.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Shimmer.NET" Version="1.3.1" />
                    
Directory.Packages.props
<PackageReference Include="Shimmer.NET" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Shimmer.NET --version 1.3.1
                    
#r "nuget: Shimmer.NET, 1.3.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Shimmer.NET@1.3.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Shimmer.NET&version=1.3.1
                    
Install as a Cake Addin
#tool nuget:?package=Shimmer.NET&version=1.3.1
                    
Install as a Cake Tool

Shimmer.NET

A high-performance, pure C# shimmer/skeleton-loading library for .NET Android. No Java/Kotlin bindings — the gradient engine, view widgets, and state machine are all native C#.

  • Content-aware maskingPorterDuff.Mode.SrcIn shimmer that hugs text and view bounds instead of painting over transparent background.
  • Synchronized sweepingShimmerGroup drives many independent shimmers off one clock for a perfectly unified sweep.
  • Built-in state machineLoading / Content / Error transitions with cross-fade, plus a generated retry view.
  • Theme-aware by default — colors adapt to light/dark mode out of the box, no configuration required.
  • Single-view widgetsShimmerTextView / ShimmerImageView shimmer on their own, no wrapping container required.
  • Reduced-motion aware — automatically falls back to a static alpha pulse when the user has disabled system animations.

Contents

Installation

<PackageReference Include="Shimmer.NET" Version="1.3.1" />

Targets net9.0-android and net10.0-android. Requires the .NET Android workload.

Core components

ShimmerDrawable

The core engine: a Drawable that builds a LinearGradient/RadialGradient shader (base → highlight → base) and repositions it every animation tick. Exposes every visual/timing knob (Direction, Shape, Intensity, Dropoff, Angle, Duration, RepeatDelay, RepeatCount, RepeatMode, Interpolator, AutoStart, ReduceMotion) so you can drive it manually if you're not using ShimmerFrameLayout.

ShimmerFrameLayout

A FrameLayout (XML tag shimmer.net.widgets.ShimmerFrameLayout) that owns a ShimmerDrawable and applies it over its children. Supports:

  • Overlay mode (default): draws the shimmer on top of your placeholder views.
  • Mask mode (app:shimmer_use_mask="true"): the shimmer only paints where child content already has non-transparent pixels — see Masking mode.
  • A ShimmerState (Loading / Content / Error) state machine via State / SetState(...), with automatic alpha cross-fading.
  • ShowError(message, retryText, onRetry) to generate and show a retry view without building your own layout.

ShimmerGroup

A LinearLayout that runs a single shared ValueAnimator and pushes its progress into every descendant ShimmerFrameLayout, so a profile header, a card, and a list can all sweep in lockstep. Children are tracked lazily and re-scanned only when the view hierarchy actually changes.

ShimmerTextView / ShimmerImageView

TextView (XML tag shimmer.net.widgets.ShimmerTextView) and ImageView (shimmer.net.widgets.ShimmerImageView) subclasses that each own their own ShimmerDrawable directly — no wrapping ShimmerFrameLayout needed for a single label or thumbnail. Same app:shimmer_* attributes, same overlay/mask draw modes, same IsLoading toggle. Mask mode only shows anything over pixels the view already renders, so an empty TextView or an ImageView with no image/background has nothing to mask onto — give it real content/background first, or leave shimmer_use_mask off (the default) to use the overlay sweep instead.

ShimmerRecyclerView

A thin RecyclerView wrapper. ShowLoading(layoutId, count) swaps in a throwaway adapter that inflates layoutId as pure, unbound placeholders; HideLoading() restores whatever adapter was set beforehand.

ShimmerPresets

Static factories that build a ready-to-add ShimmerFrameLayout (or ShimmerGroup) entirely in code — no XML needed:

  • ShimmerPresets.CreateProfile(context, cornerRadius = 8f) — circular avatar + two rounded text lines.
  • ShimmerPresets.CreatePost(context, cornerRadius = 8f) — rounded image block + rounded text lines.
  • ShimmerPresets.CreateList(context, itemCount = 5, cornerRadius = 8f)itemCount synchronized post rows inside a ShimmerGroup.

All three use density-independent dimensions (cornerRadius is in dp) and pick their placeholder color from ShimmerTheme (via GradientDrawable, not a flat rectangle), so they scale across screens, adapt to dark mode automatically, and read as rounded skeletons rather than hard-edged blocks.

XML attributes

All attributes are declared once and shared by every widget that owns a ShimmerDrawableShimmerFrameLayout, ShimmerTextView, and ShimmerImageView — under the app: namespace.

Attribute Type Notes
shimmer_base_color color Defaults to a theme-aware light gray / deep graphite if omitted.
shimmer_highlight_color color Defaults to a theme-aware highlight if omitted.
shimmer_base_alpha integer (0–255) Overrides the base color's alpha channel.
shimmer_highlight_alpha integer (0–255) Overrides the highlight color's alpha channel.
shimmer_angle float Tilt of a Linear gradient, in degrees.
shimmer_duration integer (ms) Sweep duration. Default 1000.
shimmer_repeat_count integer -1 for infinite (default).
shimmer_repeat_mode enum: restart, reverse
shimmer_repeat_delay integer (ms) Pause between sweeps.
shimmer_intensity float (0–1) Width of the highlight band.
shimmer_dropoff float (0–1) Edge softness of the highlight.
shimmer_direction enum: left_to_right, right_to_left, top_to_bottom, bottom_to_top
shimmer_shape enum: linear, radial
shimmer_use_mask boolean Enables content-aware PorterDuff masking.
shimmer_is_loading boolean Initial IsLoading/State value.
shimmer_auto_start boolean Whether entering/loading or attachment starts the animator automatically. Set false for manual Start() control. Default true.
shimmer_reduce_motion boolean Forces (or forces off) the static alpha-pulse fallback. If omitted, auto-detected from the system animator setting.

Any omitted attribute uses the documented theme-aware/default value.

Runtime API reference

ShimmerDrawable

bool IsLoading { get; set; }              // setting true starts when AutoStart is enabled
void Start();                              // ensures the animator exists and (re)starts it
void Stop();                               // cancels the animator without resetting state
bool IsStarted { get; }
float Progress { get; set; }               // used by ShimmerGroup when IsExternallyDriven
bool IsExternallyDriven { get; set; }       // true while a ShimmerGroup owns the clock
bool AutoStart { get; set; }               // false stops automatic/running animation

Color BaseColor { get; set; }
Color HighlightColor { get; set; }
float Angle { get; set; }
long Duration { get; set; }
long RepeatDelay { get; set; }
int RepeatCount { get; set; }
ValueAnimatorRepeatMode RepeatMode { get; set; }
float Intensity { get; set; }              // clamped 0..1
float Dropoff { get; set; }                // clamped 0..1
ShimmerDirection Direction { get; set; }
ShimmerShape Shape { get; set; }
bool UseMask { get; set; }
bool ReduceMotion { get; set; }            // static alpha pulse instead of a moving sweep
IInterpolator Interpolator { get; set; }

ShimmerFrameLayout

ShimmerDrawable ShimmerDrawable { get; }
ShimmerState State { get; set; }                                  // Loading | Content | Error
bool IsLoading { get; set; }                                      // convenience wrapper over State
void SetState(ShimmerState state, bool animate = true);
void SetErrorView(View errorView);
void ShowError(string message, string retryText = "Retry", Action? onRetry = null);
void Pause();                                                     // stop the sweep, keep State as-is
void Resume();                                                    // resume if still Loading

ShimmerTextView / ShimmerImageView

ShimmerDrawable ShimmerDrawable { get; }
bool IsLoading { get; set; }               // passthrough to ShimmerDrawable.IsLoading

ShimmerRecyclerView

void ShowLoading(int loadingLayoutId, int itemCount = 10);
void HideLoading();

Lifecycle & performance

  • ShimmerFrameLayout (and ShimmerTextView/ShimmerImageView) automatically stop their sweep in OnDetachedFromWindow and whenever visibility changes to anything other than Visible (window backgrounded, an ancestor hidden, an off-screen ViewPager page), then resume automatically when visible again — no wiring required.
  • For explicit control (e.g. you want to pause slightly before OnPause actually fires), call Pause() / Resume() directly; both leave State untouched, so the placeholder layout doesn't flash.
  • ShimmerDrawable reuses its gradient color/position arrays and repositions the shader via Matrix.SetLocalMatrix — no per-frame allocation. Rebuilding the shader (on color/intensity/dropoff/shape/bounds changes) disposes the outgoing Shader rather than leaking it, and mask mode reuses a single cached PorterDuffXfermode instead of allocating one per frame.
  • Every widget disposes its owned ShimmerDrawable/ValueAnimators in its own Dispose(bool) override — you don't need to do anything extra, but if you're building a custom widget on top of ShimmerDrawable directly, remember that Cancel() on a ValueAnimator doesn't release it; replace-and-dispose, don't just drop the reference.
  • Prefer ShimmerGroup over N independent ShimmerFrameLayouts when they should look synchronized: it's one ValueAnimator, not N.
  • In a RecyclerView, set all app:shimmer_* attributes via XML/style rather than one-by-one in code — ShimmerFrameLayout.Init() batches XML attributes into a single shader/animator rebuild, but setting individual properties on an already-inflated drawable rebuilds on each call.
  • If the user has disabled system animations, every shimmer widget auto-detects it and falls back to a static alpha pulse (ReduceMotion) instead of a moving sweep, so the loading indicator stays legible without violating the user's motion preference. Override with shimmer_reduce_motion if you need to force one behavior regardless of the system setting.

Masking mode

Mask mode uses Canvas.SaveLayer() + PorterDuffXfermode(PorterDuff.Mode.SrcIn) so the shimmer only appears over pixels your placeholder views already drew — ideal for text-shaped or irregularly-shaped skeletons. Trade-offs:

  • Requires an extra offscreen layer per frame, so it costs more than overlay mode.
  • On some GPU/API-level combinations you may see artifacts; if so, set shimmer_use_mask="false" (or UseMask = false) and fall back to overlay mode, optionally with SetLayerType(LayerType.Software, null).
  • On ShimmerTextView/ShimmerImageView specifically, mask mode only has something to mask onto if the view already has visible content — real Text on a ShimmerTextView, or an existing Drawable/background on a ShimmerImageView. An empty one masks onto nothing (invisible shimmer); use overlay mode (the default) for those cases instead.

Examples

XML (linear shimmer, mask mode):

<shimmer.net.widgets.ShimmerFrameLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:shimmer_is_loading="true"
    app:shimmer_use_mask="true"
    app:shimmer_duration="1500"
    app:shimmer_direction="left_to_right"
    app:shimmer_shape="linear"
    app:shimmer_intensity="0.3"
    app:shimmer_dropoff="0.5">

    

</shimmer.net.widgets.ShimmerFrameLayout>

Runtime tweaks:

var shimmer = FindViewById<Shimmer.NET.Widgets.ShimmerFrameLayout>(Resource.Id.shimmer_main);
shimmer.ShimmerDrawable.Duration = 1200;
shimmer.ShimmerDrawable.Intensity = 0.4f;
shimmer.ShimmerDrawable.Direction = Shimmer.NET.Drawables.ShimmerDirection.TopToBottom;
shimmer.IsLoading = true;

State transitions with a retry view:

shimmer.State = ShimmerState.Loading;
// ...network call fails...
shimmer.ShowError("Couldn't load your feed.", "Retry", onRetry: () => ReloadFeed());
// ...network call succeeds...
shimmer.SetState(ShimmerState.Content, animate: true);

Code-only skeleton, no XML:

var list = Shimmer.NET.Helpers.ShimmerPresets.CreateList(this, itemCount: 6, cornerRadius: 12f);
container.AddView(list);

A single shimmering label or avatar, no wrapping ShimmerFrameLayout needed:

<shimmer.net.widgets.ShimmerTextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Loading your feed..."
    app:shimmer_is_loading="true"
    app:shimmer_use_mask="true" />

<shimmer.net.widgets.ShimmerImageView
    android:layout_width="64dp"
    android:layout_height="64dp"
    android:background="@drawable/avatar_placeholder"
    app:shimmer_is_loading="true"
    app:shimmer_use_mask="true" />

Troubleshooting

  • No animation visible: confirm IsLoading/State == Loading, that the container has non-zero bounds, and that the view is actually Visible (an invisible view now pauses its sweep by design — see Lifecycle & performance).
  • Visual artifacts in mask mode: switch to overlay mode (shimmer_use_mask="false") or force a software layer, as described above.
  • FindViewById returns null for a Material component: use the concrete type, e.g. Google.Android.Material.Button.MaterialButton, not Android.Widget.Button.
  • A shimmer removed from a ShimmerGroup looks frozen: fixed in 1.2.0 — removing a child from a ShimmerGroup now resets it back to driving its own animator. If you're on an older version, set shimmerView.ShimmerDrawable.IsExternallyDriven = false manually after removal.
  • The shimmer isn't sweeping, just pulsing in place: this is ReduceMotion auto-detection, not a bug — it means Settings.Global.AnimatorDurationScale is 0 on the device/emulator (common on CI runners and emulators configured for faster UI tests). Set app:shimmer_reduce_motion="false" to force the moving sweep regardless of the system setting.

Contributing

See library_roadmap.md for the current feature roadmap and ideas under consideration. Issues and PRs welcome at the repository.

License

Shimmer.NET is released under the MIT License.

Product Compatible and additional computed target framework versions.
.NET net9.0-android35.0 is compatible.  net10.0-android was computed.  net10.0-android36.0 is compatible. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.3.1 115 8/2/2026
1.3.0 110 7/30/2026
1.2.0 115 7/24/2026
1.1.1 127 3/26/2026
1.1.0 112 3/26/2026