In this article, I will explain how you can add a caption text over the bottom of an image in Android.
The Layout Design
To add a caption over an image, I will use a relative layout that will include an ImageView and two TextView’s (in Android, the RelativeLayout allows you to overlap multiple views). I will use this image as a test image (I copied it to the res/drawable folder under the name of house.jpg):
First, we must create an activity demo class like this:
package com.ixtendo.captionimg;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
public class ImageCaptionActivity extends ActionBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_image_caption);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_image_caption, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
The activity_image_caption.xml (it can be found in the res/layout folder. If you don’t find it, then you can create it.) activity will look like this:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/relativeLayout">
<ImageView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/imageView"
android:layout_alignParentBottom="true"
android:src="@drawable/house"
android:adjustViewBounds="true" />
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginRight="5dp"
android:layout_marginBottom="5dp"
android:layout_marginLeft="5dp">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:text="Caption Title"
android:id="@+id/captionTitleView" />
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit."
android:id="@+id/captionTextView" />
</LinearLayout>
</RelativeLayout>
If you run your small application, you will see a screen like this:
As you can see, the caption is not visible. To make it visible, we need to change the color of the caption text and add a black gradient over the bottom of the picture.
Draw a Gradient over the Image
To make the caption always visible, we need to make the following changes:
- Change the color of the caption text to white.
- Add a black gradient over the bottom of the image.
To add an unobtrusive gradient over the bottom of the image, I created the following class:
package com.ixtendo.captionimg;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Shader;
import android.graphics.drawable.BitmapDrawable;
import java.io.InputStream;
public class GradientOverImageDrawable extends BitmapDrawable {
private int[] gradientColors;
private float[] gradientPositions;
private double gradientStart = 0.55;
private double gradientEnd = 1;
public GradientOverImageDrawable(Resources res, Bitmap bitmap) {
super(res, bitmap);
}
public GradientOverImageDrawable(Resources res, String filepath) {
super(res, filepath);
}
public GradientOverImageDrawable(Resources res, InputStream is) {
super(res, is);
}
public void setGradientColors(int[] gradientColors) {
this.gradientColors = gradientColors;
}
public void setGradientColors(int startColor, int... endColors) {
if (endColors.length == 0) {
throw new IllegalArgumentException("The endColors array must have at least one element");
}
gradientColors = new int[endColors.length + 1];
gradientColors[0] = startColor;
System.arraycopy(endColors, 0, gradientColors, 1, endColors.length);
}
public int[] getGradientColors() {
return gradientColors;
}
public float[] getGradientPositions() {
return gradientPositions;
}
public void setGradientPositions(float[] gradientPositions) {
for (float pos : gradientPositions) {
if (pos > 1 || pos < 0) {
throw new IllegalArgumentException("The gradient position must be a float number between 0 and 1");
}
}
this.gradientPositions = gradientPositions;
}
public double getGradientStart() {
return gradientStart;
}
public void setGradientStart(double gradientStart) {
this.gradientStart = gradientStart;
}
public double getGradientEnd() {
return gradientEnd;
}
public void setGradientEnd(double gradientEnd) {
this.gradientEnd = gradientEnd;
}
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
if (gradientColors != null) {
Rect bounds = getBounds();
int x0 = bounds.left;
int y0 = (int) Math.round(bounds.bottom * gradientStart);
int x1 = x0;
int y1 = (int) Math.round(bounds.bottom * gradientEnd);
LinearGradient shader = new LinearGradient(x0, y0, x1, y1, gradientColors, gradientPositions, Shader.TileMode.CLAMP);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setShader(shader);
canvas.drawRect(x0, y0, bounds.right, y1, paint);
}
}
}
Below you can find a short description of this class’ parameters:
- gradientColors – the colors to be distributed along the gradient line
- gradientPositions – the relative positions [0…1] of each corresponding color in the colors array
- gradientStart – the linear gradient start position. The default value is 0.55 (the gradient starts at 55% of the image height)
- gradientEnd – the linear gradient end position. The default value is 1 (the gradient will end at the bottom of the image)
Next, I will update the onCreate method in the ImageCaptionActivity class like this:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_image_caption);
TextView captionTitle = (TextView) findViewById(R.id.captionTitleView);
TextView captionBody = (TextView) findViewById(R.id.captionTextView);
ImageView imageView = (ImageView) findViewById(R.id.imageView);
captionTitle.setTextColor(Color.WHITE);
captionBody.setTextColor(Color.WHITE);
Bitmap image = BitmapFactory.decodeResource(getResources(), R.drawable.house);
int gradientStartColor = Color.argb(0, 0, 0, 0);
int gradientEndColor = Color.argb(255, 0, 0, 0);
GradientOverImageDrawable gradientDrawable = new GradientOverImageDrawable(getResources(), image);
gradientDrawable.setGradientColors(gradientStartColor, gradientEndColor);
imageView.setImageDrawable(gradientDrawable);
}
In this method, I made the following modifications:
- I searched the caption text views and set the white text color.
- I loaded the image resource from the disc as Bitmap using the BitmapFactory class.
- I defined the gradient start and end colors like this:
- start color = Color.argb(0, 0, 0, 0) – black color with full transparency
- end color = Color.argb(255, 0, 0, 0) – black color without transparency (a 255 value for the first argument of the Color.argb method means no transparency)
- I created a new instance of the GradientOverImageDrawable class by passing the loaded image to it.
- I set the new ImageDrawable instance on image view.
If you launch the Android Simulator again, the following image will be displayed:
Integration with Other Libraries
Recently, I used the Android Universal Image Loader library to optimize the image loading in my project. If you want to add a gradient over the images loaded by this library, you must do it in this way:
ImageView imageView = (ImageView) findViewById(...);
DisplayImageOptions dio = new DisplayImageOptions.Builder().displayer(new BitmapDisplayer() {
@Override
public void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom) {
int gradientStartColor = Color.argb(0, 0, 0, 0);
int gradientEndColor = Color.argb(255, 0, 0, 0);
GradientOverImageDrawable gradientDrawable = new GradientOverImageDrawable(getResources(), bitmap);
gradientDrawable.setGradientColors(gradientStartColor, gradientEndColor);
imageAware.setImageDrawable(gradientDrawable);
}
}).build();
ImageLoader.getInstance().displayImage("Your image URL", imageView, dio);
Conclusions
As we can see, adding a caption over an image in Android is not difficult. There are also other ways of adding gradients in Android using the XML method, but I didn’t obtain the same result as the one using the method described in this article.