Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Filthy Rich Android Clients

Romain Guy
November 14, 2013

Filthy Rich Android Clients

Android provides many APIs and capabilities to liven up dull UIs through graphical effects and animations. Come to this session to learn various techniques for making your mobile clients not just rich, but filthy rich.

Romain Guy

November 14, 2013
Tweet

More Decks by Romain Guy

Other Decks in Programming

Transcript

  1. Filthy Rich Clients
    Romain Guy
    Chet Haase
    google.com/+RomainGuy
    google.com/+ChetHaase
    @romainguy
    @chethaase

    View Slide

  2. Filthy Rich Clients
    Romain Guy
    Chet Haase
    google.com/+RomainGuy
    google.com/+ChetHaase
    @romainguy
    @chethaase
    Android

    View Slide

  3. Filthy Rich Clients
    Romain Guy
    Chet Haase
    google.com/+RomainGuy
    google.com/+ChetHaase
    @romainguy
    @chethaase
    Android
    Shiny!

    View Slide

  4. 2
    Definition: Filthy Rich Clients

    View Slide

  5. 2
    Definition: Filthy Rich Clients
    2007

    View Slide

  6. Ultra-graphically rich applications that ooze cool. They
    suck the user in from the outset and hang on to them
    with a death grip of excitement.
    2
    Definition: Filthy Rich Clients
    2007

    View Slide

  7. Ultra-graphically rich applications that ooze cool. They
    suck the user in from the outset and hang on to them
    with a death grip of excitement.
    2
    Definition: Filthy Rich Clients
    2007
    2013

    View Slide

  8. Ultra-graphically rich applications that ooze cool. They
    suck the user in from the outset and hang on to them
    with a death grip of excitement.
    2
    Definition: Filthy Rich Clients
    Applications that look cool, run smoothly, and interact
    well with the user.
    2007
    2013

    View Slide

  9. Graphics

    View Slide

  10. #DV13 #FilthyRichAndroid
    Images with
    rounded corners
    4

    View Slide

  11. #DV13 #FilthyRichAndroid
    Images with rounded corners
    • Don’t bake the shape in your images
    • Don’t use intermediate layers
    • Don’t use clipping
    • Use shaders!
    5

    View Slide

  12. #DV13 #FilthyRichAndroid
    What is a shader?
    6
    “A set of instructions that computes the source
    color of a pixel being drawn.”
    – Chet or Romain, just now

    View Slide

  13. #DV13 #FilthyRichAndroid
    Example
    7
    Paint p = new Paint();
    p.setColor(Color.RED);

    View Slide

  14. #DV13 #FilthyRichAndroid
    Example
    7
    Paint p = new Paint();
    p.setColor(Color.RED);
    Simplest shader ever

    View Slide

  15. #DV13 #FilthyRichAndroid
    Android shaders
    • Similar to OpenGL fragment shaders
    • Not programmable
    • Subclasses of android.graphics.Shader
    - BitmapShader
    - ComposeShader
    - LinearGradient
    - RadialGradient
    - SweepGradient
    8

    View Slide

  16. #DV13 #FilthyRichAndroid
    How drawing works (simplified)
    9
    +
    drawRoundRect
    Paint
    Mask
    Shader

    View Slide

  17. #DV13 #FilthyRichAndroid
    Back to images
    10

    View Slide

  18. #DV13 #FilthyRichAndroid
    Back to images
    10
    BitmapShader shader = new BitmapShader(bitmap,
    Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);

    View Slide

  19. #DV13 #FilthyRichAndroid
    Back to images
    10
    BitmapShader shader = new BitmapShader(bitmap,
    Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
    Paint paint = new Paint();
    paint.setAntiAlias(true);
    paint.setShader(shader);

    View Slide

  20. #DV13 #FilthyRichAndroid
    Back to images
    10
    BitmapShader shader = new BitmapShader(bitmap,
    Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
    Paint paint = new Paint();
    paint.setAntiAlias(true);
    paint.setShader(shader);
    RectF rect = new RectF(0.0f, 0.0f, width, height);
    canvas.drawRoundRect(rect, radius, radius, paint);

    View Slide

  21. #DV13 #FilthyRichAndroid
    Contrary to the previous slide…
    Never allocate in draw() methods.
    11

    View Slide

  22. #DV13 #FilthyRichAndroid
    12
    Vignette
    No vignette

    View Slide

  23. #DV13 #FilthyRichAndroid
    ComposeShader
    13
    LinearGradient
    BitmapShader ComposeShader
    xfermode

    View Slide

  24. #DV13 #FilthyRichAndroid
    Vignette
    14

    View Slide

  25. #DV13 #FilthyRichAndroid
    Vignette
    14
    RadialGradient vignette = new RadialGradient(
    mRect.centerX(), mRect.centerY(), radius,
    new int[] { 0, 0, 0x7f000000 },
    new float[] { 0.0f, 0.7f, 1.0f },
    Shader.TileMode.CLAMP);

    View Slide

  26. #DV13 #FilthyRichAndroid
    Vignette
    14
    RadialGradient vignette = new RadialGradient(
    mRect.centerX(), mRect.centerY(), radius,
    new int[] { 0, 0, 0x7f000000 },
    new float[] { 0.0f, 0.7f, 1.0f },
    Shader.TileMode.CLAMP);
    Matrix oval = new Matrix();
    oval.setScale(1.0f, 0.7f);
    vignette.setLocalMatrix(oval);

    View Slide

  27. #DV13 #FilthyRichAndroid
    Vignette
    14
    RadialGradient vignette = new RadialGradient(
    mRect.centerX(), mRect.centerY(), radius,
    new int[] { 0, 0, 0x7f000000 },
    new float[] { 0.0f, 0.7f, 1.0f },
    Shader.TileMode.CLAMP);
    Matrix oval = new Matrix();
    oval.setScale(1.0f, 0.7f);
    vignette.setLocalMatrix(oval);
    mPaint.setShader(new ComposeShader(
    mBitmapShader, vignette, PorterDuff.Mode.SRC_OVER));

    View Slide

  28. #DV13 #FilthyRichAndroid
    Works with any shape
    15
    drawCircle() drawPath() drawPath()

    View Slide

  29. Animation

    View Slide

  30. #DV13 #FilthyRichAndroid
    Animation APIs
    • View properties: ViewPropertyAnimator
    • Everything else: ObjectAnimator
    17
    view.animate().alpha(0).translationX(-500);
    ObjectAnimator.ofFloat(view, "someProperty", 0).start();

    View Slide

  31. #DV13 #FilthyRichAndroid
    Timing is Everything
    • Make those animations short!
    • And non-linear
    18

    View Slide

  32. ListView Animation!

    View Slide

  33. #DV13 #FilthyRichAndroid
    ListView Animations
    • Recycling containers are tricky
    - Views != items
    • Avoid per-frame layout
    • Determine before/after
    - animate those changes
    20

    View Slide

  34. #DV13 #FilthyRichAndroid
    Shadowed Background
    21
    protected void onDraw(Canvas canvas) {
    if (mShowing) {
    if (mUpdateBounds) {
    mShadowedBackground.setBounds(0, 0,
    getWidth(), mOpenAreaHeight);
    }
    canvas.save();
    canvas.translate(0, mOpenAreaTop);
    mShadowedBackground.draw(canvas);
    canvas.restore();
    }
    }

    View Slide

  35. #DV13 #FilthyRichAndroid
    Adapters and Stable IDs
    22
    public class StableArrayAdapter
    extends ArrayAdapter {
    // ... other methods...
    @Override
    public long getItemId(int position) {
    String item = getItem(position);
    return mIdMap.get(item);
    }
    @Override
    public boolean hasStableIds() {
    return true;
    }
    }

    View Slide

  36. #DV13 #FilthyRichAndroid
    Swiping: Move/Fade
    23
    public boolean onTouch(final View v, MotionEvent event) {
    switch (event.getAction()) {
    // skipping DOWN/CANCEL/UP events
    case MotionEvent.ACTION_MOVE:
    {
    if (!mSwiping) {
    if (deltaXAbs > mSwipeSlop) {
    mSwiping = true;
    mListView.requestDisallowInterceptTouchEvent(true);
    mBackgroundContainer.showBackground(v.getTop(),
    v.getHeight());
    }
    }
    if (mSwiping) {
    v.setTranslationX((x - mDownX));
    v.setAlpha(1 - deltaXAbs / v.getWidth());
    }
    }
    break;
    }
    }

    View Slide

  37. #DV13 #FilthyRichAndroid
    Animate out
    24
    v.animate().setDuration(duration).
    alpha(endAlpha).translationX(endX).
    withEndAction(new Runnable() {
    @Override
    public void run() {
    v.setAlpha(1);
    v.setTranslationX(0);
    animateRemoval(mListView, v);
    }
    });

    View Slide

  38. #DV13 #FilthyRichAndroid
    Animate closing the gap
    25
    private void animateRemoval(final ListView listview, View viewToRemove) {
    // [ Get startTop for all views ]
    // Delete the item from the adapter
    int position = mListView.getPositionForView(viewToRemove);
    mAdapter.remove(mAdapter.getItem(position));
    final ViewTreeObserver observer = listview.getViewTreeObserver();
    observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
    public boolean onPreDraw() {
    observer.removeOnPreDrawListener(this);
    for (int i = 0; i < listview.getChildCount(); ++i) {
    // [ get current view top ]
    child.setTranslationY(startTop - top);
    child.animate().setDuration(MOVE_DURATION).translationY(0);
    child.animate().withEndAction(new Runnable() {
    public void run() {
    mBackgroundContainer.hideBackground();
    mSwiping = false;
    mListView.setEnabled(true);
    }
    });
    }
    return true;
    }
    });
    }

    View Slide

  39. Circular Reveal!

    View Slide

  40. #DV13 #FilthyRichAndroid
    Circular reveal
    27

    View Slide

  41. #DV13 #FilthyRichAndroid
    Circular reveal
    • Technique similar to images with rounded corners
    - Uses a BitmapShader
    • The mask is not a vector shape
    • Uses an ALPHA_8 bitmap as the mask
    - Converted from any type of bitmap
    28

    View Slide

  42. #DV13 #FilthyRichAndroid
    Circular reveal
    29
    ALPHA_8
    bitmap mask
    Bitmap
    texture

    View Slide

  43. #DV13 #FilthyRichAndroid
    Capturing the content
    30
    private static Bitmap createBitmap(View target) {
    Bitmap b = Bitmap.createBitmap(
    target.getWidth(), target.getHeight(),
    Bitmap.Config.ARGB_8888);
    Canvas c = new Canvas(b);
    target.draw(c);
    return b;
    }

    View Slide

  44. #DV13 #FilthyRichAndroid
    Loading the alpha mask
    31
    private Bitmap loadAsAlphaMask(int maskId) {
    // Attempt to load the bitmap as an alpha mask
    BitmapFactory.Options opts = new BitmapFactory.Options();
    opts.inPreferredConfig = Bitmap.Config.ALPHA_8;
    Bitmap b = BitmapFactory.decodeResource(
    mRes, maskId, opts);
    // If it failed, extract the alpha
    if (b.getConfig() == Bitmap.Config.ALPHA_8) {
    return b;
    } else {
    return b.extractAlpha();
    }
    }

    View Slide

  45. #DV13 #FilthyRichAndroid
    Setting up the shader
    32
    private void createShader() {
    View target = getRootView().findViewById(mTargetId);
    mTargetBitmap = createBitmap(target);
    Shader targetShader = new BitmapShader(mTargetBitmap,
    Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
    mPaint.setShader(targetShader);
    }

    View Slide

  46. #DV13 #FilthyRichAndroid
    Drawing the spotlight
    33
    protected void onDraw(Canvas canvas) {
    mMatrix.setScale(
    1.0f / mMaskScale, 1.0f / mMaskScale);
    mMatrix.preTranslate(-getMaskX(), -getMaskY());
    mPaint.getShader().setLocalMatrix(mMatrix);
    canvas.translate(getMaskX(), getMaskY());
    canvas.scale(mMaskScale, mMaskScale);
    canvas.drawBitmap(mMask, 0.0f, 0.0f, mPaint);
    }

    View Slide

  47. #DV13 #FilthyRichAndroid
    Animating the spotlight
    34

    View Slide

  48. #DV13 #FilthyRichAndroid
    Animating the spotlight
    34
    Move left & scale up

    View Slide

  49. #DV13 #FilthyRichAndroid
    Animating the spotlight
    34
    Move to center & scale up

    View Slide

  50. #DV13 #FilthyRichAndroid
    Setting up the animations
    35
    moveLeft = ObjectAnimator.ofFloat(spot, "maskX", leftPos);
    scaleUp = ObjectAnimator.ofFloat(spot, "maskScale", scale1);
    moveCenter = ObjectAnimator.ofFloat(spot, "maskX", centerX);
    moveUp = ObjectAnimator.ofFloat(spot, "maskY", centerY);
    scaleUp2 = ObjectAnimator.ofFloat(spot, "maskScale", scale2);

    View Slide

  51. #DV13 #FilthyRichAndroid
    Choreographing
    36
    AnimatorSet set = new AnimatorSet();
    set.play(moveLeft).with(scaleUp);
    set.play(moveCenter).after(scaleUp);
    set.play(moveUp).after(scaleUp);
    set.play(scaleUp2).after(scaleUp);
    set.start();

    View Slide

  52. #DV13 #FilthyRichAndroid
    Android 4.4
    Photo Editor
    37

    View Slide

  53. #DV13 #FilthyRichAndroid
    Filter reveal
    • Same exact implementation as before
    • Draws the “spotlight“ on top of original photo
    • Spot’s position depends on where you tapped the button
    38

    View Slide

  54. #DV13 #FilthyRichAndroid
    Google Now
    39

    View Slide

  55. #DV13 #FilthyRichAndroid
    40

    View Slide

  56. #DV13 #FilthyRichAndroid
    40

    View Slide

  57. #DV13 #FilthyRichAndroid
    40
    No antialiasing!

    View Slide

  58. #DV13 #FilthyRichAndroid
    Path clipping
    41
    Path clip = new Path();
    clip.addCircle(x, y, radius,
    Path.Direction.CW);
    canvas.clipPath(clip);
    drawContent();

    View Slide

  59. #DV13 #FilthyRichAndroid
    Path clipping
    • Pros
    - Easy to implement
    - Uses less memory
    - Faster to setup (no Bitmap copy)
    • Cons
    - Android 4.3+ only with hardware acceleration
    - No antialiasing
    - Can be very expensive
    - Increases overdraw
    42

    View Slide

  60. Activity Transitions!

    View Slide

  61. #DV13 #FilthyRichAndroid
    Custom Activity Transitions
    • Standard window animations
    - default: scale/fade
    - customize: slide, fade, scale
    - Also thumbnail scale/crossfade
    • ... But that’s it
    • Totally custom requires in-activity animations
    44

    View Slide

  62. #DV13 #FilthyRichAndroid
    Custom Activity Transitions
    • Disable window animations
    • Animate exiting activity
    • Launch new activity with transparent window
    • Animate content when activity comes up
    45

    View Slide

  63. View Slide

  64. #DV13 #FilthyRichAndroid
    Grayscale thumbnails
    47
    ColorMatrix grayMatrix = new ColorMatrix();
    grayMatrix.setSaturation(0);
    ColorMatrixColorFilter grayscaleFilter =
    new ColorMatrixColorFilter(grayMatrix);
    thumbnailDrawable.setColorFilter(grayscaleFilter);

    View Slide

  65. #DV13 #FilthyRichAndroid
    Drop shadow container
    48
    protected void onDraw(Canvas canvas) {
    for (int i = 0; i < getChildCount(); ++i) {
    View child = getChildAt(i);
    if (child.getVisibility() != View.VISIBLE || child.getAlpha() == 0) {
    continue;
    }
    int depthFactor = (int) (80 * mShadowDepth);
    canvas.save();
    canvas.translate(child.getLeft() + depthFactor,
    child.getTop() + depthFactor);
    canvas.concat(child.getMatrix());
    tempShadowRectF.right = child.getWidth();
    tempShadowRectF.bottom = child.getHeight();
    canvas.drawBitmap(mShadowBitmap, sShadowRect,
    tempShadowRectF, mShadowPaint);
    canvas.restore();
    }
    }

    View Slide

  66. #DV13 #FilthyRichAndroid
    Drop shadow depth
    49
    public void setShadowDepth(float depth) {
    if (depth != mShadowDepth) {
    mShadowDepth = depth;
    mShadowPaint.setAlpha(
    (int) (100 + 150 * (1 - mShadowDepth)));
    invalidate();
    }
    }

    View Slide

  67. #DV13 #FilthyRichAndroid
    Transparent activity background
    50
    ...
    android:theme="@style/Transparent" >

    <br/><item name="android:windowNoTitle">true</item><br/><item name="android:windowIsTranslucent">true</item><br/><item name="android:windowBackground">@android:color/transparent</item><br/>

    View Slide

  68. #DV13 #FilthyRichAndroid
    Launch sub-activity
    51
    int[] screenLocation = new int[2];
    v.getLocationOnScreen(screenLocation);
    PictureData info = mPicturesData.get(v);
    int orientation = getResources().getConfiguration().orientation;
    Intent subActivity = new Intent(ActivityAnimations.this,
    PictureDetailsActivity.class);
    subActivity.putExtra(PACKAGE + ".orientation", orientation).
    putExtra(PACKAGE + ".resourceId", info.resourceId).
    putExtra(PACKAGE + ".left", screenLocation[0]).
    putExtra(PACKAGE + ".top", screenLocation[1]).
    putExtra(PACKAGE + ".width", v.getWidth()).
    putExtra(PACKAGE + ".height", v.getHeight()).
    putExtra(PACKAGE + ".description", info.description);
    startActivity(subActivity);
    overridePendingTransition(0, 0);

    View Slide

  69. #DV13 #FilthyRichAndroid
    Get animation start values
    52
    Bundle bundle = getIntent().getExtras();
    Bitmap bitmap = BitmapUtils.getBitmap(getResources(),
    bundle.getInt(PACKAGE_NAME + ".resourceId"));
    String description = bundle.getString(PACKAGE_NAME + ".description");
    final int thumbnailTop = bundle.getInt(PACKAGE_NAME + ".top");
    final int thumbnailLeft = bundle.getInt(PACKAGE_NAME + ".left");
    final int thumbnailWidth = bundle.getInt(PACKAGE_NAME + ".width");
    final int thumbnailHeight = bundle.getInt(PACKAGE_NAME + ".height");
    mOriginalOrientation = bundle.getInt(PACKAGE_NAME + ".orientation");

    View Slide

  70. #DV13 #FilthyRichAndroid
    Get animation end values
    53
    ViewTreeObserver observer = mImageView.getViewTreeObserver();
    observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
    @Override
    public boolean onPreDraw() {
    mImageView.getViewTreeObserver().removeOnPreDrawListener(this);
    int[] screenLocation = new int[2];
    mImageView.getLocationOnScreen(screenLocation);
    mLeftDelta = thumbnailLeft - screenLocation[0];
    mTopDelta = thumbnailTop - screenLocation[1];
    mWidthScale = (float) thumbnailWidth / mImageView.getWidth();
    mHeightScale = (float) thumbnailHeight / mImageView.getHeight();
    runEnterAnimation();
    return true;
    }
    });

    View Slide

  71. #DV13 #FilthyRichAndroid
    Animate thumbnail & description
    54
    mImageView.setPivotX(0);
    mImageView.setPivotY(0);
    mImageView.setScaleX(mWidthScale);
    mImageView.setScaleY(mHeightScale);
    mImageView.setTranslationX(mLeftDelta);
    mImageView.setTranslationY(mTopDelta);
    mTextView.setAlpha(0);
    mImageView.animate().setDuration(duration).
    scaleX(1).scaleY(1).
    translationX(0).translationY(0).
    setInterpolator(sDecelerator).
    withEndAction(new Runnable() {
    public void run() {
    mTextView.setTranslationY(-mTextView.getHeight());
    mTextView.animate().setDuration(duration/2).
    translationY(0).alpha(1).
    setInterpolator(sDecelerator);
    }
    });

    View Slide

  72. #DV13 #FilthyRichAndroid
    Fade in black background
    55
    ObjectAnimator.ofInt(mBackground, "alpha", 0, 255).
    start();

    View Slide

  73. #DV13 #FilthyRichAndroid
    Colorize thumbnail
    56
    ObjectAnimator colorizer = ObjectAnimator.ofFloat(
    PictureDetailsActivity.this, "saturation", 0, 1);
    colorizer.start();
    public void setSaturation(float value) {
    colorizerMatrix.setSaturation(value);
    ColorMatrixColorFilter colorizerFilter =
    new ColorMatrixColorFilter(colorizerMatrix);
    mBitmapDrawable.setColorFilter(colorizerFilter);
    }

    View Slide

  74. #DV13 #FilthyRichAndroid
    Animate drop shadow
    57
    ObjectAnimator shadowAnim = ObjectAnimator.ofFloat(
    mShadowLayout, "shadowDepth", 0, 1);
    shadowAnim.start();

    View Slide

  75. #DV13 #FilthyRichAndroid
    Animate back to main activity
    58
    @Override
    public void onBackPressed() {
    runExitAnimation(new Runnable() {
    public void run() {
    finish();
    }
    });
    }
    @Override
    public void finish() {
    super.finish();
    overridePendingTransition(0, 0);
    }

    View Slide

  76. Folding Layout!

    View Slide

  77. #DV13 #FilthyRichAndroid
    Fan Fare
    60

    View Slide

  78. #DV13 #FilthyRichAndroid
    61

    View Slide

  79. #DV13 #FilthyRichAndroid
    62
    Matrix.setPolyToPoly()

    View Slide

  80. #DV13 #FilthyRichAndroid
    63
    for (int x = 0; x < mNumberOfFolds; x++) {
    src = mFoldRectArray[x];
    canvas.save();
    canvas.concat(mMatrix[x]);
    canvas.clipRect(0, 0, src.width(), src.height());
    canvas.translate(-src.left, 0);
    super.dispatchDraw(canvas);
    if (x % 2 == 0) {
    canvas.drawRect(0, 0, mFoldW, mFoldH, mSolidShadow);
    } else {
    canvas.drawRect(0, 0, mFoldW, mFoldH, mSoftShadow);
    }
    canvas.restore();
    }

    View Slide

  81. #DV13 #FilthyRichAndroid
    63
    for (int x = 0; x < mNumberOfFolds; x++) {
    src = mFoldRectArray[x];
    canvas.save();
    canvas.concat(mMatrix[x]);
    canvas.clipRect(0, 0, src.width(), src.height());
    canvas.translate(-src.left, 0);
    super.dispatchDraw(canvas);
    if (x % 2 == 0) {
    canvas.drawRect(0, 0, mFoldW, mFoldH, mSolidShadow);
    } else {
    canvas.drawRect(0, 0, mFoldW, mFoldH, mSoftShadow);
    }
    canvas.restore();
    }

    View Slide

  82. #DV13 #FilthyRichAndroid
    63
    for (int x = 0; x < mNumberOfFolds; x++) {
    src = mFoldRectArray[x];
    canvas.save();
    canvas.concat(mMatrix[x]);
    canvas.clipRect(0, 0, src.width(), src.height());
    canvas.translate(-src.left, 0);
    super.dispatchDraw(canvas);
    if (x % 2 == 0) {
    canvas.drawRect(0, 0, mFoldW, mFoldH, mSolidShadow);
    } else {
    canvas.drawRect(0, 0, mFoldW, mFoldH, mSoftShadow);
    }
    canvas.restore();
    }

    View Slide

  83. #DV13 #FilthyRichAndroid
    63
    for (int x = 0; x < mNumberOfFolds; x++) {
    src = mFoldRectArray[x];
    canvas.save();
    canvas.concat(mMatrix[x]);
    canvas.clipRect(0, 0, src.width(), src.height());
    canvas.translate(-src.left, 0);
    super.dispatchDraw(canvas);
    if (x % 2 == 0) {
    canvas.drawRect(0, 0, mFoldW, mFoldH, mSolidShadow);
    } else {
    canvas.drawRect(0, 0, mFoldW, mFoldH, mSoftShadow);
    }
    canvas.restore();
    }

    View Slide

  84. #DV13 #FilthyRichAndroid
    Color Filters
    • Can be used to modify a shader
    • Subclasses of ColorFilter
    - ColorMatrixColorFilter
    - LightingColorFilter
    - PorterDuffColorFilter
    64

    View Slide

  85. #DV13 #FilthyRichAndroid
    Sepia Effect
    65
    ColorMatrix m1 = new ColorMatrix();
    ColorMatrix m2 = new ColorMatrix();
    m1.setSaturation(0.1f);
    m2.setScale(1f, 0.95f, 0.82f, 1.0f);
    m1.setConcat(m2, m1);
    mSepiaPaint.setColorFilter(
    new ColorMatrixColorFilter(m1));

    View Slide

  86. Performance

    View Slide

  87. #DV13 #FilthyRichAndroid
    Smoother is Better
    • Consistent frame rate
    • Avoid hiccups
    • Avoid large steps over few frames
    67

    View Slide

  88. #DV13 #FilthyRichAndroid
    Only Draw What You Need (ODWYN)
    • Prefer invalidate(l, t, r, b) over invalidate()
    • Only invalidate custom views that actually change
    • Let the framework invalidate standard views
    68

    View Slide

  89. #DV13 #FilthyRichAndroid
    Avoid Overdraw
    • Developer options -> Show Overdraw
    • Window background vs. opaque containers vs. opaque views
    69

    View Slide

  90. #DV13 #FilthyRichAndroid
    Get Off that UI Thread!
    • Avoid expensive operations on UI thread
    - network, database, bitmaps, ...
    • AsyncTask is your friend
    70

    View Slide

  91. #DV13 #FilthyRichAndroid
    Avoid Garbage Collection
    • ... especially during animations
    • Lots of small objects will eventually cause GC
    • Avoid Iterators, temporary objects
    - Consider cached objects for temporaries
    • Use Allocation Tracker in DDMS
    71

    View Slide

  92. #DV13 #FilthyRichAndroid
    clipPath
    • Not always the fastest way to clip to a path
    • Doesn’t support antialiasing
    • Try BitmapShader
    72

    View Slide

  93. #DV13 #FilthyRichAndroid
    Consider Time Travel
    • Go see Android Performance Workshop 2 days ago
    - Memory
    - Performance tips
    - Tools
    - Case studies
    73

    View Slide

  94. #DV13 #FilthyRichAndroid
    For More Information
    • Google I/O talks
    • Parleys.com talks
    • Devbytes on YouTube
    74
    Chet
    graphics-geek.blogspot.com
    google.com/+ChetHaase
    @chethaase
    Romain:
    curious-creature.org
    google.com/+RomainGuy
    @romainguy

    View Slide

  95. #DV13 #FilthyRichAndroid
    Q&A
    75
    Filthy Rich Clients: Developing
    Animated and Graphical Effects

    View Slide