[Java for Android App Development] frangulyan.dev:
I want to share some codes of a small effect that I implemented in my Android app called Arithmetic Puzzles. This is also a chance for me to listen to other people and make improvements. At the end of this post there is a link to the app so that you can see the code in action.

It is a text shining effect which reacts on device movements. It creates a feeling of glass surface of the text which shines and reflects light. Only outline of the text is shining.

Please note that the text in my case was very short - a number with 2 digits - which looks cool. If you will try a longer text then let me know how it looks :)

I grabbed some parts of my code from its context to put here so if there is something missing or irrelevant then just let me know.

So, here we go! The text shine is done extending a simple View:

public class EquationView extends View {

...
// the text to be drawn
private String mText;
private Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Paint mTextShinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private int mShineColor;
private int mShineNoColor;
...

// constructors
public EquationView(Context context) {
super(context);
init(context);
}

public EquationView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}

public void initialize(Context context) {
// paints
setupPaint(mTextPaint, R.color.text_color, context); // text color (yellow for me)
setupPaint(mTextShinePaint, R.color.text_shine, context); // shine color (white for me)
mTextShinePaint.setDither(true);
mTextShinePaint.setStyle(Paint.Style.STROKE);
mTextShinePaint.setStrokeJoin(Paint.Join.ROUND);
mTextShinePaint.setStrokeMiter(10);

// colors
mTextShadowColor = context.getResources().getColor(R.color.text_shadow); (#AA454210 for me)
mShineColor = context.getResources().getColor(R.color.text_shine); (white for me)
mShineNoColor = context.getResources().getColor(android.R.color.transparent);
}

private void setupPaint(Paint paint, int colorId, Context context) {
paint.setColor(context.getResources().getColor(colorId));
paint.setTextAlign(Paint.Align.CENTER);
}

The initialization function is setting up the paint objects. Since I am going to use LinearGradient as a shader in my paint, I am setting the dither to true. I have already wrote about it in my previous guide. I am also setting the shine paint style to STROKE so that only the outline of the text is "shining".

The stroke parameters like join and miter are mostly set to make it look prettier.

I also set the text align to CENTER. This has effect during drawing of the text - when I tell the paint to draw a text at some (x, y) point, then x is considered as the center point of the whole text, thus text is centered horizontally on this origin.

Next, let's have a look at the onDraw() function:

...
private static final float TEXT_HEIGHT = 0.8f;
private static final float SHINE_THICKNESS = 0.015f;
private final float mShadowBlurRadius = 5.0f * getResources().getDisplayMetrics().density; // 5dp

private LinearGradient mShineGradient;
private int mTextShadowColor;
private float mShadowShiftX = 0.0f;
private float mShadowShiftY = 0.0f;
private Rect mBounds = new Rect();
private Rect mTextBounds = new Rect();
...

@Override
protected void onDraw(Canvas canvas) {
// step 1. collect information needed for drawing
canvas.getClipBounds(mBounds);
float centerX = mBounds.centerX();
float h = mBounds.height();
float textSize = h * TEXT_HEIGHT;
float textCenterY = mBounds.top + h * 0.5f;

// step 2. draw the shadows
mTextPaint.setShadowLayer(mShadowBlurRadius, mShadowShiftX, mShadowShiftY, mTextShadowColor);
drawText(mText, centerX, textCenterY, textSize, canvas, mTextPaint);

// step 3. draw the shine
if (mShineGradient != null) {
mTextShinePaint.setShader(mShineGradient);
mTextShinePaint.setStrokeWidth(TextSize * SHINE_THICKNESS);
drawText(mText, centerX, textCenterY, textSize, canvas, mTextShinePaint);
}

// step 4. draw the text
mTextPaint.clearShadowLayer();
drawText(mText, centerX, textCenterY, textSize, canvas, mTextPaint);
}

private void drawText(String text, float centerX, float centerY,
float size, Canvas canvas, Paint paint) {
paint.setTextSize(size);
paint.getTextBounds(text, 0, text.length(), mTextBounds);
canvas.drawText(text, centerX, centerY + mTextBounds.height() / 2.0f - mTextBounds.bottom, paint);
}

In step 1 I collect information for drawing like clip bounds (see previous guide for clip bounds), sizes and positions.

In step 2 I draw the shadows. I also want the shadows to move when device moves (on gyroscope events). I actually draw the text with shadows - the text will be overdrawn in next steps so only shadows will be left from this step. The offsets of the shadows - mShadowShiftX and mShadowShiftY - are updated based on gyroscope data.

In step 3 I draw the shine. I set the LinearGradient mShineGradient as the shader of the paint and then set the stroke width to SHINE_THICKNESS. Then I draw text with that shader. For more information on shaders see my previous guide. The mShineGradient is updated when new gyroscope data is received. We will come to this gradient creation later.

In step 4 I disable the shadows and draw the text again. It will overwrite only the text, not the shadows and not the shine, so I have only the outline shining.

A common drawText() function is used to draw the text. It first sets the text size (font size), then calculates the text bounds using getTextBounds(). This is needed to center the text around the origin point also in vertical direction since Paint.Align.CENTER is aligning only in horizontal direction.

TO BE CONTINUED in the thread, seems there is limit on post size...