The Canvas class holds the "draw" calls. To draw something, you need 4 basic components: A Bitmap to hold the pixels, a Canvas to host the draw calls (writing into the bitmap), a drawing primitive (e.g. Rect, Path, text, Bitmap), and a paint (to describe the colors and styles for the drawing).

The Canonical Way

We can start with something easy. Let’s imagine we have to show a png and need to add a border to it.

A very simple solution for a static layout might be introducing an XML shape as a background. Consider this very basic layout implementation:

Dynamic Variation

Here at PSPDFKit, we care greatly about performance and are always pushing the limits of Android for providing the best possible experience. In PSPDFKit 3.1 for Android introduced a scrollable thumbnail bar, used to navigate a document in a more visual way.
This new feature also offers an easy way for our framework clients to customize many details, such as thumbnail size, border width, color, spacing, and more.

Recycling the views is one of the practical ways to provide a smooth UI when dealing with long lists. And remember: bitmaps can be recycled as well!

A simple BorderedImageView example that includes the use of a png image, border width, and color, can be seen below.

publicclassBorderedImageViewextendsImageView{privatestaticfinalintSTROKE_WIDTH_DP=6;privatePaintpaintBorder;privateBitmapbitmap;privateintstrokeWidthPx;privateRectFrectF;/** Simple constructor. */publicBorderedImageView(Contextcontext){super(context);init();}privatevoidinit(){// The resource is embedded, but it can be easily moved in the constructor.bitmap=BitmapFactory.decodeResource(getResources(),R.drawable.pspdfkit_icon);// The same goes for the stroke width in dp.strokeWidthPx=(int)(STROKE_WIDTH_DP*getResources().getDisplayMetrics().density);inthalfStrokeWidthPx=strokeWidthPx/2;paintBorder=newPaint();paintBorder.setStyle(Paint.Style.STROKE);// Stroke width is in pixels.paintBorder.setStrokeWidth(strokeWidthPx);// Our color for the border.paintBorder.setColor(Color.BLUE);inttotalWidth=bitmap.getWidth()+strokeWidthPx*2;inttotalHeight=bitmap.getHeight()+strokeWidthPx*2;// An empty bitmap with the same size of our resource to display, increased of the desired border width.setImageBitmap(Bitmap.createBitmap(totalWidth,totalHeight,Bitmap.Config.ARGB_8888));// The rectangle that will be used for drawing the colored border.rectF=newRectF(halfStrokeWidthPx,halfStrokeWidthPx,totalWidth-halfStrokeWidthPx,totalHeight-halfStrokeWidthPx);}@OverrideprotectedvoidonDraw(Canvascanvas){super.onDraw(canvas);// A rounded rect will be printed.canvas.drawRoundRect(rectF,40,40,paintBorder);// The bitmap for the resource R.drawable.pspdfkit_icon.// Note the Paint for the bitmap is null, we'll talk about this in a moment...canvas.drawBitmap(bitmap,strokeWidthPx,strokeWidthPx,null);}}

With the above, we can easily make all the details customizable programmatically.

The Paint parameter for drawing the bitmap was left null on purpose. This is perfectly fine for drawing the bitmap as it is, but it opens a window to a lot of nifty improvements.
Do you want a taste?

Down The Rabbit Hole.

Basic image manipulation of your ImageView instances is often required, and Matrix manipulations and Shader effects are here to help.

A very common one use case is rotations: you want to keep your code properly encapsulated implementing your RotatingImageView that extends ImageView and you don't want to rely on something like RotateAnimation at the layout level.

publicclassRotatingImageViewextendsImageView{// Initial position.privateintrotationDegrees=0;publicRotatingImageView(Contextcontext){super(context);init();}publicRotatingImageView(Contextcontext,@NullableAttributeSetattrs){super(context,attrs);init();}publicRotatingImageView(Contextcontext,@NullableAttributeSetattrs,intdefStyleAttr){super(context,attrs,defStyleAttr);init();}privatevoidinit(){Bitmapbitmap=BitmapFactory.decodeResource(getResources(),R.drawable.pspdfkit_icon);setImageBitmap(bitmap);}@OverrideprotectedvoidonDraw(Canvascanvas){// Translate rotation axe to the center.canvas.translate(canvas.getWidth()/2,canvas.getHeight()/2);// Rotate!canvas.rotate(rotation(3));// Put back to its original place.canvas.translate(-canvas.getWidth()/2,-canvas.getHeight()/2);// Invalidate the view.postInvalidateOnAnimation();super.onDraw(canvas);}privateintrotation(intdelta){rotationDegrees=(rotationDegrees+delta)%360;returnrotationDegrees;}}

Shaders Shaders Shaders

Shader is the based class for objects that return horizontal spans of colors during drawing. A subclass of Shader is installed in a Paint calling paint.setShader(shader). After that any object (other than a bitmap) that is drawn with that paint will get its color(s) from the shader.

Conclusion

Android Shaders and low-level Canvas calls may come helpful to enrich the UX and create eye-catching effects under some constraints, without performance degradation. Nevertheless, when complex manipulations are required, stick to the KISS principle without reinventing the wheel is the way to go. Android provides extensive APIs using scenes and transitions that you can also take a look! 😎