Saturday, May 21, 2011

Stretching Rows in Android TableLayout




Google's Android SDK allows developers to architect a UI from the comfort and safety of XML. This is done by creating a layout resource *.xml file under the /res/layout directory of the project.

Hand-coded UIs can be difficult to maintain and update. For example, grid-based layouts can become complex and unwieldy as they evolve. An XML-based layout tries to address these problems, and the Android Developers forum provides helpful tutorials on the subject.

As I studied the layout tutorials, I hoped to use percentage values for the android:layout_height and android:layout_width attributes to evenly stretch buttons in a TableLayout. Since percentage values are not allowed however, another solution was required.

The android:stretchColumns attribute addresses part of the problem. For example, a 3-column table layout can be evenly laid out--horizontally, at least--by setting android:stretchColumns="0,1,2". But the rows are not stretched and no corresponding android:stretchRows exists.

The solution actually depends android:stretchColumns as well as a few other layout attributes and can be outlined as follows:
  1. give the top-level TableLayout node in the XML file an android:weightSum value corresponding to the number of rows in the layout. If the layout has 3 rows, then set android:weightSum=3.
  2. give each TableRow node within the TableLayout an android:layout_weight attribute and set the value such that the sum of all the android:layout_weight values from all the nodes within is equal to the value of android:weightSum in the containing TableLayout.
  3. set the android:layout_height and android:layout_width of the items inside the TableRow elements ( in my case, a Button element ), to "fill_parent."


With this approach, I was able to achieve the above pictured layout without hard-coding any numeric values in the XML for height or width. Here is the XML that I used as a layout resource in my test Android app:




android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:weightSum="3"
android:stretchColumns="0,1,2">


android:id="@+id/pad1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="1">


android:id="@+id/pad2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="2">


android:id="@+id/pad3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="3">





android:id="@+id/pad4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="4">


android:id="@+id/pad5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="5">


android:id="@+id/pad6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="6">






android:id="@+id/pad8"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="8">


android:id="@+id/pad9"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="9">





Friday, March 4, 2011

Test WebKit Rendering with Qt

Using the QtWebKit module provided by Qt, developers can test WebKit rendering without visual inspection. The QWebView class from QtWebKit, along with a few classes from QtGui, can be used to verify rendering in relatively few lines of code.

The basic approach creates a QImage by rendering a QWebView's frame to a QPainter, whose paint device is a QImage instance. Once QWebView has rendered to the QImage, this object can be simply compared with another QImage.

This is creating a screenshot of a web page and comparing it to another screenshot to confirm or refute that the images are the same. The images can be optionally saved to a file if visual proof is desired.

For example, given two QWebView objects, a screenshot for each can be generated and then compared:


QWebView viewA, viewB;
QImage screenShotA, screenShotB;
WebKitRenderTest renderTest;

screenShotA = renderTest.initWebView(QColor(Qt::red), viewA, html, QString("viewA"));

screenShotB = renderTest.initWebView(QColor(Qt::blue), viewB, html, QString("viewB"));

QString result = (screenShotA == screenShotB) ? "images are same" : "images are different";


Note that the comparison is done with the overloaded equality operator in QImage.

The WebKitRenderTest is just a helper class with little more than a function for initializing a QWebVIew, setting the HTML content, applying a background color via QWebView's QPalette, and returning a QImage--the screenshot. Here is the body of such a function:


QImage WebKitRenderTest::initWebView(QColor bgColor, QWebView& view, QString& content, QString imgName){
QPalette pal;
QBrush brush(bgColor);
brush.setStyle(Qt::SolidPattern);
pal.setBrush(QPalette::Base, brush);

view.setPalette(pal);
view.setHtml(content);
view.page()->setViewportSize(view.page()->currentFrame()->contentsSize());

QImage img(view.page()->viewportSize(), QImage::Format_ARGB32);
QPainter paintView(&img);
view.page()->currentFrame()->render(&paintView);
paintView.end();

QString imgFileName = imgName + ".png";
img.save(imgFileName); // not necessary, just for visual inspection
view.close();

return img;
}




By using the QPalette::Base color role on the brush, the entire page background can be colored as desired by the application.

The technique is straightforward and easily adapted for automated tests. It is especially suited for use with the QTestLib framework provided by Qt.

A compilable, buildable project based on this example can be found here in zipped form:
https://docs.google.com/uc?id=0B7qXaguuORTFNWM4MjE1MGMtMTg4Yi00YzQ4LTk2ZjYtYzczZjQ1NzFiOTMw&export=download&authkey=COfEpIoB&hl=en

To see how this approach is already being used in WebKit, see this Changeset:
http://trac.webkit.org/changeset/79409