Result

Example 5 (Shadow Color)

Shadows are filled by a linear gradient, so shadow color is built of two compontents shadow_start_color, which defines the start color of the gradient and shadow_end_color, which defines the end color of the gradient.

Tip : To get a solid color shadow, just put the same value in shadow_start_color and shadow_end_color attributes.

Result

Example 7 (Non-Transparent background)

So far we have seen that the library generates long shadows for all non-transparent parts in an image, but what if we want the image to have a non-transparent background, but only create shadow for what is inside the image and not the background. This is where background_transparent and background_color_to_ignore attributes come into play.

If your image has say a white background, with some character in the middle and we only want the shadow for the middle character and completely avoid shadow for the white background, you can set background_transparent as false, indicating the background contains some color other that 0 (transparent), and background_color_to_ignore as #FFFFFF, indicating that we need to treat all white pixels as background and not generate shadow for them.

Note : Transparent pixels are always counted as background and never generate any shadow of their own. This is useful in cases where the background of your image's background is a circle or a rounded-rectangle with white color, where we have to count both transparent pixels and white pixel as background.

e.g. Lets say we want to display the google icon which has a white rectangle that acts as the background of the letter G. Lets see how can we generate long shadow only for the letter and avoid building the shadow for the rectangular white background.

For this example we will create a circular shape drawable rectangular_white_background.xml which will act as the background to icon vector ic_google_icon_colored.xml. You can find these resource files here

Result

Note : As much as I want people to use this feature, I should warn that this won't work in several cases as android internally applies anti alias on images/bitmaps while scaling and this leads to some random colors around the boundaries. Since the algorithm checks for only one particular color, these new colors will be treated as contours and shadow will be generated, leading to unexpected results.

So if you want to have a background, I would suggest to draw the background in a different View and overlay this View with LongShadowsView/LongShadowsImageView/LongShadowsTextView, containing only the part you want to generate the shadow for.

If you are creating a custom view and want to draw a bitmap and use this feature, you may set Paint#setFilterBitmap() to false. This will disable anti aliasing for the bitmap.

Dynamically updating shadows

All the shadow parameters can be updated dynamically through java. Here is a small example :

LongShadowsImageView longShadowsImageView = (LongShadowsImageView) findViewById(R.id.long_shadows_image_view);
longShadowsImageView.setShadowAngle("135");
// Set various paramters here
// Call the update method at the end, for the changes to take effect
longShadowsImageView.update();

Note : You need to call update() method at the end for the changes to take effect. This is done to avoid updating views again and again as the parameters are changed. This approach ensures that the views are updated only when the user is done with changing the parameters.

Custom Views and ViewGroups

Custom View

If you want your own custom view to be able to generate long shadows, just extend LongShadowsView instead of View while creating the custom view.That's it :)

Note : The custom view must be inside LongShadowsWrapper or LongShadowsFrameLayoutWrapper for the shadow to be generated.

Custom View Group

LongShadowsWrapper and LongShadowsFrameLayoutWrapper are two custom view groups provided by library that extend RelativeLayout and FrameLayout respectively. These custom view groups recursively search for eligible views for long shadow generation, even inside nested view groups.

So if you have a custom view group, inside which you want to place your LongShadowsImageView or LongShadowsTextView, then you can just make this view group a direct child of LongShadowsWrapper or LongShadowsFrameLayoutWrapper and the library will take care of everything else.

How does this work ?

The LongShadowsWrapper is an extension of Relative Layout. The LongShadowsFrameLayoutWrapper is an extension of FrameLayout. Use any one of them as per your convenience.

Firstly, eligible child views (LongShadowsImageView, LongShadowsTextView or extensions of LongShadowsView) are found using a recursive strategy. Once an eligible view is found it goes through the following process to generate the long shadow.

First a bitmap is generated from the drawing cache of the View. The bitmap is converted into an integer array of pixels and passed on to the native function getShadowPaths(...) along with width, height and some other parameters.

The native function first generates contours using the contours function. The contours function returns vector points which are on the boundary of an object. This is achieved by doing BFS(Breath First Search) on the grid and marking the points which are on the boundary and were encountered in the BFS run.

Each of the contour is then passed to getShadowPathsFromContour function, which internally calls boundaryPath function. The boundaryPath function returns the Front Polar Path of the object. Front Polar Path is the set of points which will be directly visible from the Light Source, in polar order. We need the points in Polar Order, so that we can remove darkening of some regions due to overlapping, which will result in some dark regions during rendering due to alpha blending.

3.1. To find the Front polar order, we first extract the outer boundary of an object because for objects like 'O', there are two boundaries. This is done through getOuterBoundary function. In this function, we do BFS travesal from a point on outer boundary. So all the points which are not visited/marked after the BFS travesal belong to the inner boundary.

3.2. Now we have the Outer Boundary of the object. Now we will seperate the Boundary which will be visible by the Light source(lets name this boundary as Front Path) from the Overall Boundary. Also, notice that the Front Path will begin from the Upper tangent to the Lower tangent (If tangents are drawn from the Light Source to the Object). So we again apply BFS from Upper Tangent to Lower Tangent. This is done in the function getPath.

3.3. We still cannot be sure that the path returned by getPath is the Front Path, as there are 2 paths from the Upper Tangent to the Lower Tangent. So we compare the Area formed by the Light Source and the 2 Paths. The one with the less area will be the Front Path. getArea function calculates the area of the figure formed by the Light Source and the path by using Shoelace Formulae.

3.4. Corner Cases handling - We have used atan function to get the angle, inorder to sort the points in Polar Order. But atan function is discountinuos. So we have carefully handled the discountinuity of the function in the getPolarOrder function.

Once we have the Front Polar Path, we translate its end points according to the shadow length and shadow angle.

Now all these points are collected and converted into a structure ShadowPath. The getShadowPathsFromContour functions returns one ShadowPath object for every contour.

All ShadowPath objects are passed back to Java, where Path objects are created using the points.

Finally the Path object created is rendered and filled with a LinearGradient with starting and end points as defined in the ShadowPath object.

P.S. : All the algorithms used in the native code have been implemented by my friend Piyush Mehrotra.

Documentation

Attributes for LongShadowsWrapper and LongShadowsFrameLayoutWrapper

XML attribute

Java set methods

Description

Default Value

shouldClipChildren

setShouldClipChildren(...)

Set whether to clip children to bounds

false

shouldClipToPadding

setShouldClipToPadding(...)

Set whether to clip children to padding

false

calculateAsync

setShouldCalculateAsync(...)

Set the flag for async shadow calculations.

true

showWhenAllReady

setShowShadowsWhenAllReady(...)

Set the flag for showing all shadows after all calculations are over

true

animateShadow

setShouldAnimateShadow(...)

Set the flag for shadow animation

true

animationDuration

setAnimationDuration(...)

Set the value of shadow animation duration.

300ms

Attributes for LongShadowsImageView, LongShadowsTextView and LongShadowsView