Sunday, May 19, 2013

Android UI - Modifying Android views

In the previous part we discussed essential concepts of Views in Android, so now it's time to try something on practice. Today we'll create something like this:



As you can see, it's an image with a counter (of something) in the bottom-right corner.

From this arcticle and farther: by "widget" I mean some implementations of android.view.View that located in android.widget package (such as TextView, ImageView, EditText, etc.)

Where to start?

We have four different options to complete our task (arranged by increasing of complexity):
  1. Search for an existing one - indeed, maybe someone already acomplished it (or even maybe it is a default Android widget). However, sometimes it's faster (and may be more reliable, depending on your experience) to create your own implementation than dealing with someone's code (or bugs).
  2. Compose view from default Views - it's pretty simple to just create your own layout, fill it with views and arrange them in a required order.
  3. Extend some existing View - when all you need is slightly modify the appearance (or behaviour) of default widgets it's common to just override some key methods of base class (widget), use default implementation (by calling super) and then provide your own details. This approach also provides you power of polymorphism, so you can continue to use base-class in your code.
  4. Create your own View (or ViewGroup, in case you're working with layouts) - this is the most flexible (as well as most complicated) way to create your own unique behaviout and appearance of View.
Let's skip first option and continue with the rest.

Development


Composing custom View from existing one may sound great, but there is two problems. First of all - we can't fully use power of canvas-drawing. In our case - we can't draw such "transparent" text (in other words - hole) over the circle. Ok, let's forget about effects (imagine that we actually need just black or white text), we can compose our simplified View from FrameLayout with ImageView and TextView inside. Here is approximate XML:



But second problem arises. Adding additional layout and views requires additional measurement and layout processes, which causes additional overhead. Here how it may look like:
  1. Parent of FrameLayout will try to measure it.
  2. To find out it's size FrameLayout will need to find out the size of both ImageView and TextView (which is not that simple even for TextView). + 2 calls of onMeasure
  3. Now, when childs of FrameLayout was measured, it can finally calculate it's own size. + 1 call of onMeasure (well, actually it was called in first step, but it was completed just now).
  4. Now, when FrameLayout is measured, it's time to layout it's childs (to achieve required appearance). Additional computations.
  5. Finally, FrameLayout will draw itself and it's childs.
On practice, however, you won't notice significant difference. But, when there will be a lot of such Views, it may affect overall performance.

On the other hand, it's not so hard to create your own fast and efficient View for this purpose. So, since we're working with image, we'll most likely need all that behaviour and usefull methods provided by ImageView class. Then let's extend it!



Looks like it's time for explanation :)

First things first - we want to keep all standart look and behaviour (except our custom appearance), so we're keeping all constructors from the base class.

What we are actually doing in this View is creating Bitmap with our white circle and text, and then drawing it on canvas right above drawings of the base class (ImageView). Here is step-by-step process:

  • We creating new Bitmap inside onMeasure method if there was no bitmap before (it was null) or if size of our view has changed. Also, we're creating Canvas that represents our Bitmap, so we can draw directly into it.
  • Inside onSizeChanged we're calculating radius of our white circle.
  • Then onDraw method is invoked, which requires additional explanation. First, we're calling super version to draw default ImageView appearance.
  • onDraw: we're drawing our white circle into our Bitmap (using Canvas) using mCirclePaint (instance of Paint class, which describes parameters of our drawings, such as color).
  • onDraw: we're drawing out text into Bitmap. Key moment here is that we're using Paint (mTextPaint) with Porter-Duff Xfer mode. I'll provide additional explanation in next articles, but for now I'll just say that in our case it "erases" area on our white circle, so it will look like hole (shaped like text).
  • onDraw: now, when we finished drawing into our Bitmap, we draw Bitmap itself into Canvas provided as a method argument.
And here is the result:


In the next part we'll cover Xfer modes in more details.

Saturday, May 11, 2013

Android UI - Introduction to Views

This is the first article in "Android UI" series. Since this is some kind of "intro", we'll just cover some basic aspects of View drawing.

Further in the text, View (with capital V) will represent concrete class (or subclasses) android.view.View. While view (with lowercase v) will represent general block of UI without bounding to concrete implementation.

What is a View?

View is an essential component of every graphical OS (not just Android). It may be called in many ways (such as UIView in iOS), although they all share the same concept. So, what is a "view"? In short: it is a region on the screen that you can see and with which you can interact.

Respectively, view is responsible for:
  1. Measure itself. In other words - decide how big region of the screen it will require.
  2. Drawing itself within it's region.
  3. Handling user interaction.
Particularly in Android OS view is represented by android.view.View class. Description of this class covers almost all aspects of working with views, so I won't copy-and-paste them here. Instead, I highly recommend to read it. Although, to made a conclusion, here is a steps that should be completed (typically by Android, not by you) in order to draw View:
  1. View should be instantinated by calling one of the constructors (there is no surprise). Typical place for initialization processes, such as loading parameters from XML or inflating some layout (again, from XML). More about that below.
  2. View will be measured to find out how big it will be. onMeasure(int, int) method will be invoked (most likely it will be perfomed by Android automatically, so you don't need to call it manually).
  3. View will be layouted (most likely by it's parent), so it will "know" it's position on the screen (left and right bounds as well as width and height). For View, you don't need to take any steps, here all is done for you.
  4. At least, View will be drawed. All drawing process is perfomend inside draw(Canvas) method.
Once again, detailed description of View drawing process is described in official reference

I mentioned that View is typically measured and layout by it's parent (which is also measured by it's parent, and so on, until it comes to Android OS, which perfoms all this process automatically for you). But, you can also go through this process by yourself in case you're crazy really need this. More about this in next articles.

Out of the box.

Note, that View itself is non-abstract class, which means that it can be instantinated, have some default behaviour and can be drawn as well as other views. So, what do we have by default? Most importantly:
  • Any instance of View can measure itself, simply by taking width and height params from XML (wrap_content, match_parent, or exact value in dp).
  • Any instance of View have android:background property, so it can... draw it's background.
And this is great, because:
  1. It will save some time while developing your own Views (since you shouldn't add background by yourself).
  2. You can compose custom painted area just by setting background property of View. This is very useful when you need, for example, underline below some view:


This XML will result in horizontal black line in 1dp width. Althougth, it seems prety simple - there is a better way to do such things, which I'll cover in the next article: Modifying views.

P.S. Sorry, this article was kinda short. I've decided to not overload first one with tons of code, but there definately will be some code in the next article. Stay tuned.

Friday, April 26, 2013

Introduction

Let's start. This blog is mainly about explanation of not-so-trivial things in Android development. It's aimed on somewhat experienced Android developers (please, don't excpect "Hello World" examples). However, I think that with a bit of tention anyone can jump in (at least I'll try to write in a such manner).

Before we'll start, little disclaimer. As you might noticed, I'm not so fluence in English. I hope this won't be a problem (there will be a lot of code, which is pretty international).

This blog will consist of few "courses" (for now):

  • Android UI - deep course into how Views in Android works, how to create custom views, some tricks, etc.
  • Robust applications - this course is mainly about how to create applications that won't crash on most devices (or at least on yours) under different circumstances.
  • Bad practices - I think there is enough articles about "good" practices in Android development, so we'll discuss commin mistakes in app architecture.

I'll try to keep this blog weekly (so, you'll have a week to experiment with new skills).

Android UI will start from 11 May, while other courses will start approximately at July-August.