Cdacians

Cdacians
Cdacians

Thursday, 7 September 2017

Gradle tip #1: tasks

Gradle tip #1: tasks


With this post I would like to start series of Gradle-related topics I wish I knew when I first started writing Gradle build scripts.
Today we will talk about Gradle tasks and specifically configuration and executionparts of the task. Since these terms might appear unknown to vast majority of readers, it will be easier to have real examples. Essentially (sorry for looking ahead), we will try to figure out what is the difference between these 3 examples:
task myTask {
    println "Hello, World!"
}

task myTask {
    doLast {
        println "Hello, World!"
    }
}

task myTask << {
    println "Hello, World!"
}
My goal - is to create a task which prints "Hello, World!" when I execute it.
When I first started, my first guess was to implement it like this:
task myTask {
    println "Hello, World!"
}
Now, let's try to execute my new task!
user$ gradle myTask
Hello, World!
:myTask UP-TO-DATE
It seems to be working! It prints "Hello, World!".
But! It doesn't work as we might expect it to work. And here is why. Let's try to call gradle tasks to see what other tasks are available:
user$ gradle tasks
Hello, World!
:tasks

------------------------------------------------------------
All tasks runnable from root project
------------------------------------------------------------

Build Setup tasks
-----------------
init - Initializes a new Gradle build. [incubating]
..........
Wait a second! Why my "Hello, World!" string is printed? I just called tasks, I didn't call my custom task!
The reason why this is happening - is that Gradle task has 2 major stages in its lifecycle:
  • Configuration stage
  • Execution stage
I might not be super precise with terminology here, but this analogy helped me to understand tasks.
The thing is that Gradle has to configure all tasks specified in build script beforeactual build is started. It doesn't matter if certain task will be executed - it still needs to be configured.
Knowing that, how do I know which part of my task is evaluated during configuration and which one during execution?
And the answer is - the part specified within the top-level of the task - is task configuration section. I.e:
task myTask {
    def name = "Pavel" //<-- this is evaluated during configuration
    println "Hello, World!"////<-- this is also evaluated during configuration
}
That's why when I call gradle tasks I can see "Hello, World!" - this is our configuration section is executed. But that's not really what I want - I want "Hello, World!" to be printed only when I explicitly call my task.
So how do I tell Gradle to do something when my tasks is executed?
In order to do that I need to specify task Action. The easiest way to specify task action is via Task#doLast() method:
task myTask {
    def text = 'Hello, World!' //configure my task
    doLast {
        println text //this is executed when my task is called
    }
}
Now my "Hello, World!" string will be printed only when I explicitly call gradle myTask
Cool, now I know how to configure and make my task do real work only when I invoke it. What about that third option with << symbol?:
task myTask2 << {
    println "Hello, World!" 
}
This version is just a shortcut of doLast version, i.e. it is exactly the same as I would write:
task myTask {
    doLast {
        println 'Hello, World!' //this is executed when my task is called
    }
}
However, since now everything goes into execution part, I cannot configure my task the same way I did it with doLast option (it is still possible to do, but in a slightly different way). So this option is good for really small tasks which do not require configuration, but if you have some task other than printing a "Hello, World!" - you might consider going with doLast.
Happy gradling!

Advanced blurring techniques

Advanced blurring techniques

Today we will try to dig a bit deeper into blurring techniques available for Android developers. I read couple of articles and SO posts describing different ways to do this, so I want to summarize what I learned.

Why?

More and more developers now try to add different kinds of blurry backgrounds for their custom views. Take a look at awesome Muzei app by +RomanNurik or Yahoo Weather app. I really like what they did with the design there.
I was inspired to write this article by set of blog posts from here (by Mark Allison). So the first part of this post will be really similar to Mark's post. But I will try to go even further.
Basically what we will try to accomplish today is the following:

final result

Prerequisites

Let me describe what I will be working with. I have 1 activity which hosts different fragments in a ViewPager. Every fragment represent 1 blurring technique.
Here is what my layout_main.xml looks like:
<android.support.v4.view.ViewPager
xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/pager"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.paveldudka.MainActivity" />
And here is my fragment_layout.xml:
<?xml version="1.0" encoding="utf-8"?>

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/picture"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:src="@drawable/picture"
        android:scaleType="centerCrop" />

    <TextView
        android:id="@+id/text"
        android:gravity="center_horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="My super text"
        android:textColor="@android:color/white"
        android:layout_gravity="center_vertical"
        android:textStyle="bold"
        android:textSize="48sp" />
    <LinearLayout
        android:id="@+id/controls"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#7f000000"
        android:orientation="vertical"
        android:layout_gravity="bottom"/>
</FrameLayout>
As you can see this is just an ImageView with TextView centered and some debug layout (@+id/controls) I will use to display performance measurements and add some more tweaks.
The general blurring technique looks like:
  • Cut that part of background which is behind my TextView
  • Blur it
  • Set this blurred part as background to my TextView

Renderscript

The most popular answer to questions like "how do I implement blur in Android" is - Renderscript. This is very powerful and optimized "engine" to work with graphics. I will not try to explain how it works under the hood (since I don't know either :) and this is definitely out of scope for this post).
public class RSBlurFragment extends Fragment {
    private ImageView image;
    private TextView text;
    private TextView statusText;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_layout, container, false);
        image = (ImageView) view.findViewById(R.id.picture);
        text = (TextView) view.findViewById(R.id.text);
        statusText = addStatusText((ViewGroup) view.findViewById(R.id.controls));
        applyBlur();
        return view;
    }

    private void applyBlur() {
        image.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
            @Override
            public boolean onPreDraw() {
                image.getViewTreeObserver().removeOnPreDrawListener(this);
                image.buildDrawingCache();

                Bitmap bmp = image.getDrawingCache();
                blur(bmp, text);
                return true;
            }
        });
    }

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
    private void blur(Bitmap bkg, View view) {
        long startMs = System.currentTimeMillis();

        float radius = 20;

        Bitmap overlay = Bitmap.createBitmap((int) (view.getMeasuredWidth()),
                (int) (view.getMeasuredHeight()), Bitmap.Config.ARGB_8888);

        Canvas canvas = new Canvas(overlay);

        canvas.translate(-view.getLeft(), -view.getTop());
        canvas.drawBitmap(bkg, 0, 0, null);

        RenderScript rs = RenderScript.create(getActivity());

        Allocation overlayAlloc = Allocation.createFromBitmap(
                rs, overlay);

        ScriptIntrinsicBlur blur = ScriptIntrinsicBlur.create(
                rs, overlayAlloc.getElement());

        blur.setInput(overlayAlloc);

        blur.setRadius(radius);

        blur.forEach(overlayAlloc);

        overlayAlloc.copyTo(overlay);

        view.setBackground(new BitmapDrawable(
                getResources(), overlay));

        rs.destroy();
        statusText.setText(System.currentTimeMillis() - startMs + "ms");
    }

    @Override
    public String toString() {
        return "RenderScript";
    }

    private TextView addStatusText(ViewGroup container) {
        TextView result = new TextView(getActivity());
        result.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
        result.setTextColor(0xFFFFFFFF);
        container.addView(result);
        return result;
    }
}
  • When fragment gets created - I inflate my layout, add TextView to my debug panel (I will use it to display blurring performance) and apply blur to the image
  • Inside applyBlur() I register onPreDrawListener(). I need this because at the moment my applyBlur() is called nothing is laid out yet, so there is nothing to blur. I need to wait until my layout is measured, laid out and is ready to be displayed.
  • In onPreDraw() callback first thing I usually do is change generated false return value to true. It is really important to understand that if you return false - the frame which is about to be drawn will be skipped. I am actually interested in the first frame, so I return true.
  • Then I remove my callback because I don't want to listen to pre-draw events anymore.
  • Now I want to get Bitmap out of my ImageView. I build drawing cache and retrieve it by calling getDrawingCache()
  • And eventually blur. Let's discuss this step more precisely.
I want to say here that I realize that my code doesn't cover couple of very important moments:
  • It doesn't re-blur when layout changes. For this you need to register onGlobalLayoutListener and repeat blurring whenever layout changes
  • It does blurring in the main thread. Obviously is not the way you do it in production, but for the sake of simplicity, I will do that for now :)
So, let's go back to my blur():
  • At first I create an empty bitmap to copy part of my background into. This bitmap I will blur later and set as a background to my TextView
  • Create Canvas backed up by this bitmap
  • Translate canvas to the position of my TextView within parent layout
  • Draw part of my ImageView to bitmap
  • At this point I have a bitmap equals to my TextView size and containing that part of my ImageView which is behind the TextView
  • Create Renderscript instance
  • Copy my bitmap to Renderscript-friendly piece of data
  • Create Renderscript blur instance
  • Set input, radius and apply blur
  • Copy result back to my bitmap
  • Great! Now we have blurred bitmap. Let's set it as a background to my TextView
Here is what I got:
Renderscript-blurred
As we can see, result is pretty good and it took 57ms. Since one frame in Android should render no more than ~16ms (60fps) we can see that doing that on UI thread will drop our frame rate down to 17fps for the period of blurring. Obviously is not acceptable, so we need to offload this to AsyncTask or something similar.
Also it worth mentioning that ScriptIntrinsicBlur is available from API 17 only, but you can use renderscript support lib to lower required API a bit.
But still, a lot of us still have to support older APIs which don't have this fancy renderscript support. Let's find out what we can do here.

FastBlur

Since blur process is nothing more than just pixel manipulation, obvious solution would be to try do blurring manually. Luckily, there are plenty examples of Java implementation of blur. The only thing we need to do is to find relatively quick implementation.
Thanks to this post on SO, I picked fast blur implementation. Let's see what does it look like.
I will describe only blur function since the rest of the code is the same:
private void blur(Bitmap bkg, View view) {
    long startMs = System.currentTimeMillis();
    float radius = 20;

    Bitmap overlay = Bitmap.createBitmap((int) (view.getMeasuredWidth()),
            (int) (view.getMeasuredHeight()), Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(overlay);
    canvas.translate(-view.getLeft(), -view.getTop());
    canvas.drawBitmap(bkg, 0, 0, null);
    overlay = FastBlur.doBlur(overlay, (int)radius, true);
    view.setBackground(new BitmapDrawable(getResources(), overlay));
    statusText.setText(System.currentTimeMillis() - startMs + "ms");
}
And here is result:
Fast blur As we can see, quality of blur is pretty much the same.
So, the benefit of using FastBlur is that we eliminated renderscript dependency (and removed min API constraint).
But damn! It takes hell a lot of time! We spent 147ms doing blur! And this is far not the slowest SW blurring algorithm. I don't event want to try Gaussian blur...

Going beyond

Now let's think what can we do better. Blurring process itself is all about "losing" pixels. You know what else is all about losing pixels? Right! Downscaling!
What if we try to downscale our bitmap first, do blur and then upscale it again. I tried to implement this technique and here is what I got:
downscaling
Well, look at that! 13ms for renderscript and 2ms for FastBlur. Not bad at all!
Let's look at the code. I describe only fastblur approach since it the same for renderscript. Full code you can check in my GitHub repo.
private void blur(Bitmap bkg, View view) {
    long startMs = System.currentTimeMillis();
    float scaleFactor = 1;
    float radius = 20;
    if (downScale.isChecked()) {
        scaleFactor = 8;
        radius = 2;
    }

    Bitmap overlay = Bitmap.createBitmap((int) (view.getMeasuredWidth()/scaleFactor),
            (int) (view.getMeasuredHeight()/scaleFactor), Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(overlay);
    canvas.translate(-view.getLeft()/scaleFactor, -view.getTop()/scaleFactor);
    canvas.scale(1 / scaleFactor, 1 / scaleFactor);
    Paint paint = new Paint();
    paint.setFlags(Paint.FILTER_BITMAP_FLAG);
    canvas.drawBitmap(bkg, 0, 0, paint);

    overlay = FastBlur.doBlur(overlay, (int)radius, true);
    view.setBackground(new BitmapDrawable(getResources(), overlay));
    statusText.setText(System.currentTimeMillis() - startMs + "ms");
}
Let's go through the code:
  • scaleFactor tells what level of downscale we want to apply. In my case I will downscale my bitmap to 1/8 of its original size. Also since my bitmap will be blurred by downscaling/upscaling process, I don't need that big radius for my blurring algo. I decided to go with 2.
  • Now I need to create bitmap. This bitmap will be 8 times smaller than I finally need for my background.
  • Also please note that I provided Paint with FILTER_BITMAP_FLAG. In this way I will get bilinear filtering applied to my bitmap during scaling. It will give me even smoother blurring.
  • As before, apply blur. In this case image is smaller and radius is lower, so blur is really fast.
  • Set blurred image as a background. This will automatically upscale it back again.
It is interesting that fastblur did blurring even faster than renderscript. That's because we don't waste time copying our bitmap to Allocation and back.
With these simple manipulations I managed to get relatively fast blurring mechanism w/o renderscript dependency.
WARNING! Please note that FastBlur uses hell a lot of additional memory (it is copying entire bitmap into temp buffers), so even if it works perfect for small bitmaps, I would not recommend using it for blurring entire screen since you can easily get OutOfMemoryException on low-end devices. Use your best judjement
Source code for this article is available on GitHub

Fragments translate animation

Fragments translate animation

During one of my recent assignments I had to implement a Fragment which slides up from the bottom when you open it and slides back down when you close it. Something like this:
My gif
Here list view is a separate ListFragment and when I press "list" action item - I add this fragment to the main activity.
Lets find out how to do that and what problems you might be facing here.

setCustomAnimations

The very first thing I found is FragmentTransaction#setCustomAnimations() method which lets you specify custom enter/exit animations for certain fragment transaction.
Neat! Looks like exactly what I need. The only thing I need to do - is to create custom Animator via XML and pass it into my fragment transaction:
slide_up.xml
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:interpolator="@android:anim/accelerate_decelerate_interpolator"
        android:propertyName="translationY"
        android:valueType="floatType"
        android:valueFrom="1280"
        android:valueTo="0"
        android:duration="@android:integer/config_mediumAnimTime"/>
slide_down.xml
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:interpolator="@android:anim/accelerate_decelerate_interpolator"
        android:propertyName="translationY"
        android:valueType="floatType"
        android:valueFrom="0"
        android:valueTo="1280"
        android:duration="@android:integer/config_mediumAnimTime"/>
fragment toggle routine:
private void toggleList() {
    Fragment f = getFragmentManager()
                       .findFragmentByTag(LIST_FRAGMENT_TAG);
    if (f != null) {
        getFragmentManager().popBackStack();
    } else {
        getFragmentManager().beginTransaction()
                .setCustomAnimations(R.animator.slide_up,
                        R.animator.slide_down,
                        R.animator.slide_up,
                        R.animator.slide_down)
                .add(R.id.list_fragment_container, Fragment
                                .instantiate(this, SlidingListFragment.class.getName()),
                        LIST_FRAGMENT_TAG
                ).addToBackStack(null).commit();
    }
}

Problem #1: Order matters

Please note that order in which you call methods of your fragment transaction matters! In my case I call setCustomAnimations before add. If you swap these calls - animation will not be specified.

Problem #2: Hardcoded values are bad

Unfortunately, it is not possible to specify relative translation in ObjectAnimator (like 100%, -50%, etc.), so we had to hardcode target translation. Don't want to even explain why this is bad.

setYFraction()

Definitely hardcoded translation values is not acceptable approach for me, so let's see what we can do here.
Even if ObjectAnimator doesn't have relative translation attribute, it doesn't mean we cannot create our own one. Essentially we can animate any built-in object property (accessible via setter method) or even create our own one.
Having said that, let's create custom RelativeLayout and add yFraction attribute which we will be animating via ObjectAnimator:
SlidingRelativeLayout.java:
package com.trickyandroid.fragmenttranslate.app.view;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.RelativeLayout;

public class SlidingRelativeLayout extends RelativeLayout {

    public SlidingRelativeLayout(Context context) {
        super(context);
    }

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

    public SlidingRelativeLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public void setYFraction(final float fraction) {
        float translationY = getHeight() * fraction;
        setTranslationY(translationY);
    }

    public float getYFraction() {
        if (getHeight() == 0) {
            return 0;
        }
        return getTranslationY() / getHeight();
    }
}
Here I created custom attribute yFraction which represents current Y translation relative to view's height (fraction 0.5 = 50% of height). I.e. now if we animate our fraction value from 0 to 1 - we will animate translationY from 0% to 100%
Now let's set this custom layout as a root layout for my fragment:
sliding_fragment_layout.xml
<?xml version="1.0" encoding="utf-8"?>

<com.trickyandroid.fragmenttranslate.app.view.SlidingRelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#7c7c7c">

    <ListView
            android:id="@android:id/list"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>

</com.trickyandroid.fragmenttranslate.app.view.SlidingRelativeLayout>
And let's update my animation resources:
slide_up.xml:
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:interpolator="@android:anim/accelerate_decelerate_interpolator"
        android:propertyName="yFraction"
        android:valueType="floatType"
        android:valueFrom="1.0"
        android:valueTo="0"
        android:duration="@android:integer/config_mediumAnimTime"/>
slide_down.xml
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:interpolator="@android:anim/accelerate_decelerate_interpolator"
        android:propertyName="yFraction"
        android:valueType="floatType"
        android:valueFrom="0"
        android:valueTo="1.0"
        android:duration="@android:integer/config_mediumAnimTime"/>
Let's see what we've got:
yFraction
WTF? It seems like it slides correctly, but the very first frame in slide_up animation is not translated :(
The problem is that custom fragment animation starts before our layout is measured. It means that our getHeight() is '0' thus translationY is '0' as well when it should be 100%. To confirm this let's add onPreDrawListener() to our custom layout and dump current yFractiontranslationY and getHeight() values:
logcat
Every setYFraction call represents a call from our animator. Here you can see that first 2 calls were made when layout height was '0', but yFraction is 1 which means it should be translated by entire height of the view.
And when the first frame is about to render - translation is set incorrectly.

setYFraction(). Revised

The problem is clear - we need to remember yFraction value and update our translationY once view is measured but just before it is rendered. onPreDraw() suits the best here:
SlidingRelativeLayout.java
package com.trickyandroid.fragmenttranslate.app.view;

import android.content.Context;
import android.util.AttributeSet;
import android.view.ViewTreeObserver;
import android.widget.RelativeLayout;

public class SlidingRelativeLayout extends RelativeLayout {

    private float yFraction = 0;

    public SlidingRelativeLayout(Context context) {
        super(context);
    }

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

    public SlidingRelativeLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    private ViewTreeObserver.OnPreDrawListener preDrawListener = null;

    public void setYFraction(float fraction) {

        this.yFraction = fraction;

        if (getHeight() == 0) {
            if (preDrawListener == null) {
                preDrawListener = new ViewTreeObserver.OnPreDrawListener() {
                    @Override
                    public boolean onPreDraw() {
                        getViewTreeObserver().removeOnPreDrawListener(preDrawListener);
                        setYFraction(yFraction);
                        return true;
                    }
                };
                getViewTreeObserver().addOnPreDrawListener(preDrawListener);
            }
            return;
        }

        float translationY = getHeight() * fraction;
        setTranslationY(translationY);
    }

    public float getYFraction() {
        return this.yFraction;
    }
}
Let's see what we got:
fixed animation
Gif puts a lot of distortion, but on a device this animation looks really smooth and silky.

Bonus

One neat trick would also help with our "missing" first frame. Along with translate animation you can also animate alpha (from 0 to 1). This will cause first frame to be fully transparent, so you don't see it :) No need to mention that it is not a solution, but rather a workaround..:
slide_up.xml:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <objectAnimator
            android:interpolator="@android:anim/accelerate_decelerate_interpolator"
            android:propertyName="yFraction"
            android:valueType="floatType"
            android:valueFrom="1.0"
            android:valueTo="0"
            android:duration="@android:integer/config_mediumAnimTime"/>
    <objectAnimator
            android:interpolator="@android:anim/accelerate_decelerate_interpolator"
            android:propertyName="alpha"
            android:valueType="floatType"
            android:valueFrom="0"
            android:valueTo="1.0"
            android:duration="@android:integer/config_mediumAnimTime"/>
</set>
That's kinda it. With these not very complex manipulations we got pretty nice looking fragment animation.