Or Continue to Thread: [GUIDE] Text shine effect with…
Find Your Device:
24th August 2014, 10:13 AM   |  #2  
OP Junior Member
Flag Ulm
Thanks Meter: 11
27 posts
Join Date:Joined: Aug 2014
CONTINUATION of the guide

Now lets see how is the mShineGradient created. This is done every time we got a new data from gyroscope:

    // all the magic numbers here and in below function are results of experiments
    private static final float MAX_ANGLE = (float)(Math.PI / 2.0);
    private final float mShadowMaxShift = 5.0f * getResources().getDisplayMetrics().density; // 5dp

    public void gyroChanged(float xAngle, float yAngle) {
        // 1. shadows
        float loweredMax = MAX_ANGLE / 4;
        mShadowShiftX = (xAngle / loweredMax) * mShadowMaxShift;
        mShadowShiftY = (yAngle / loweredMax) * mShadowMaxShift;

        // put in [-mShadowMaxShift, mShadowMaxShift] range
        if (mShadowShiftX > mShadowMaxShift) mShadowShiftX = mShadowMaxShift;
        if (mShadowShiftX < -mShadowMaxShift) mShadowShiftX = -mShadowMaxShift;
        if (mShadowShiftY > mShadowMaxShift) mShadowShiftY = mShadowMaxShift;
        if (mShadowShiftY < -mShadowMaxShift) mShadowShiftY = -mShadowMaxShift;

        // 2. shine
        float angleX = xAngle / MAX_ANGLE;
        float angleY = yAngle / MAX_ANGLE;

        // put in [-1, 1] range
        if (angleX > 1.0f) angleX = 1.0f;
        if (angleX < -1.0f) angleX = -1.0f;
        if (angleY > 1.0f) angleY = 1.0f;
        if (angleY < -1.0f) angleY = -1.0f;

        createShineGradient(angleX, angleY);

        // redraw
The numbers and formulas are quite experimental, so you can play around to find the best numbers for your case. The meaning and usage of gyroChanged() function is explained in my previous guide.

The basic idea behind is to get the shine position based on device's rotation in X and Y direction. I convert the rotation into a range from -1 to 1 using some max angle that I defined. If both X and Y angles are -1 then the shine line is in the lower left corner of the text, if both are 1 then in upper right corner, otherwise somewhere in between.

Here is the createShineGradient() function:

    private static final float SHINE_WIDTH = 0.07f;
    private static final float SHINE_BLUR_WIDTH = 0.05f;

    private void createShineGradient(float relativeX, float relativeY) {
        if ((mBounds == null) || (mBounds.width() == 0) || (mBounds.height() == 0)) {
            mShineGradient = null;

        // we want to scale the angles' range and take inner part of
        // length 1 this will speed up the shine without sudden stops
        final float SPEED_FACTOR = 4.0f;
        relativeX *= SPEED_FACTOR;
        relativeY *= SPEED_FACTOR;

        float boxSize = mBounds.height() * 1.2f; // make the text box a bit bigger
        float left = mBounds.centerX() - boxSize / 2.0f;
        float top = mBounds.top;

        // project the (relativeX, relativeY) point to the diagonal
        float relative = (relativeX + relativeY) / 2.0f;
        // shift by 0.5 to get a point from (0, 1) range
        relative += 0.5f;

        int[] colors = {mShineNoColor, mShineNoColor, mShineColor, mShineColor, mShineNoColor, mShineNoColor};
        float[] positions = {0.0f, clamp(relative - SHINE_WIDTH - SHINE_BLUR_WIDTH),
                             clamp(relative - SHINE_WIDTH), clamp(relative + SHINE_WIDTH),
                             clamp(relative + SHINE_WIDTH + SHINE_BLUR_WIDTH), 1.0f};

        mShineGradient = new LinearGradient(left, top + boxSize, left + boxSize, top,
                                            colors, positions, Shader.TileMode.CLAMP);

    private float clamp(float value) {
        if (value < 0.0f) {
            return 0;
        if (value > 1.0f) {
            return 1.0f;
        return value;
Again, there are a lot of experimental stuff, you might want to play with it to come to a good solution. The LinearGradient shader is explained in my previous guide. However, here we use more colors so that we can have a white stripe in the middle with small color change gradients on borders. The picture below explains everything:

linear gradient with multiple colors

The idea is to project the relative angle of the device rotation in X and Y direction to a single point on the View's diagonal through which the shine will pass. This projection should result in continuous and more or less natural movement of the shine line during device rotation, the formula I used is a result of my tries and errors.

When a new gradient is created invalidate() is called and the view redraws itself.

Finally, to see this code in action, you can have a look at the app itself, its free:

Google Play:
Arithmetic Puzzles on Google Play

Direct link:

It would be nice to get some comments and suggestions, let me know your thoughts!
The Following 2 Users Say Thank You to frangulyan.dev For This Useful Post: [ View ]