Tags

, , , , , , , , , , , , , , , , ,

While implementing lightweight themes for mobile, a decision was made to use the same images as the desktop, as there are plenty of personas available at getpersonas.com. However there was a problem. The images are of size 3000×200 pixels and are optimized for the desktop browser. But the mobile phones are usually in portrait format. Also, the height of the device will definitely be more than 200 pixels (Samsung S IV is 441 ppi!). How do we use the same image in mobile then? We resorted to using the dominant color for the background. But the place where the image and dominant color matches will be an abrupt shift. How do we fix this? We decided to gradually fade the image and merge it with the background color.

Let’s see how we can gradually fade-in an image. The reflected image in the screenshot has android:rotationX set to 180, and is a separate image.

The idea is to use a Shader with the Paint used for drawing on the Canvas. We want the image to be opaque on the top and transparent at the bottom. A LinearGradient from black to transparent could achieve this effect.

public class ReflectedImageView extends ImageView {
    private Paint mPaint;
    
    public ReflectedImageView(Context context, AttributeSet attrs) {
        super(context, attrs);

        // Setup the paint.
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setColor(0xFFFF0000);
        mPaint.setStrokeWidth(0.0f);
        
        // Destination (DST) is drawn by the parent, and should be retained.
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
    }
    
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        
        // It's recommended not to create a Shader in the basic layout calls.
        // Linear gradient from Transparent to Black, from top to bottom.
        // Note: We rotated the image using a transformation.
        // Hence the colors will be opposite.
        LinearGradient gradient = new LinearGradient(0, 0, 0, bottom - top, 
                                                     0x0, 0xFF000000,
                                                     Shader.TileMode.CLAMP);
        
        // Set the gradient as the shader.
        mPaint.setShader(gradient);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        // Save the canvas. All PorterDuff operations should be done in a offscreen bitmap.
        int count = canvas.saveLayer(0, 0, canvas.getWidth(), canvas.getHeight(), null,
                                     Canvas.MATRIX_SAVE_FLAG |
                                     Canvas.CLIP_SAVE_FLAG |
                                     Canvas.HAS_ALPHA_LAYER_SAVE_FLAG |
                                     Canvas.FULL_COLOR_LAYER_SAVE_FLAG |
                                     Canvas.CLIP_TO_LAYER_SAVE_FLAG);


        
        // Do a default draw.
        super.onDraw(canvas);
        
        // Draw the paint (that has a shader set), on top of the image
        // drawn by the parent (ImageView).
        // Note: This works only on ICS. For pre-ICS phones, create a bitmap and
        // draw on it, like mentioned in CanvasDelegate linked below.
        canvas.drawPaint(mPaint);
        
        // Restore the canvas.
        canvas.restoreToCount(count);
    }
}

That gives a nice glossy feel for the image. This method was earlier used in drawing the tabs button. To re-use the logic, Fennec uses a LightweightThemeDrawable, that uses a CanvasDelegate, which takes care of drawing on the canvas. Grab an aurora build today to give this a try!

Update: A better approach would be to use Shaders. This reduces the need for a offscreen bitmap.

About these ads