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

Filthy Rich Android Clients

237be48129b762b31847d6167597366d?s=47 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.

237be48129b762b31847d6167597366d?s=128

Romain Guy

November 14, 2013
Tweet

Transcript

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

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

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

    @chethaase Android Shiny!
  4. 2 Definition: Filthy Rich Clients

  5. 2 Definition: Filthy Rich Clients 2007

  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
  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
  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
  9. Graphics

  10. #DV13 #FilthyRichAndroid Images with rounded corners 4

  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
  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
  13. #DV13 #FilthyRichAndroid Example 7 Paint p = new Paint(); p.setColor(Color.RED);

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

    Simplest shader ever
  15. #DV13 #FilthyRichAndroid Android shaders • Similar to OpenGL fragment shaders

    • Not programmable • Subclasses of android.graphics.Shader - BitmapShader - ComposeShader - LinearGradient - RadialGradient - SweepGradient 8
  16. #DV13 #FilthyRichAndroid How drawing works (simplified) 9 + drawRoundRect Paint

    Mask Shader
  17. #DV13 #FilthyRichAndroid Back to images 10

  18. #DV13 #FilthyRichAndroid Back to images 10 BitmapShader shader = new

    BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
  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);
  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);
  21. #DV13 #FilthyRichAndroid Contrary to the previous slide… Never allocate in

    draw() methods. 11
  22. #DV13 #FilthyRichAndroid 12 Vignette No vignette

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

  24. #DV13 #FilthyRichAndroid Vignette 14

  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);
  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);
  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));
  28. #DV13 #FilthyRichAndroid Works with any shape 15 drawCircle() drawPath() drawPath()

  29. Animation

  30. #DV13 #FilthyRichAndroid Animation APIs • View properties: ViewPropertyAnimator • Everything

    else: ObjectAnimator 17 view.animate().alpha(0).translationX(-500); ObjectAnimator.ofFloat(view, "someProperty", 0).start();
  31. #DV13 #FilthyRichAndroid Timing is Everything • Make those animations short!

    • And non-linear 18
  32. ListView Animation!

  33. #DV13 #FilthyRichAndroid ListView Animations • Recycling containers are tricky -

    Views != items • Avoid per-frame layout • Determine before/after - animate those changes 20
  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(); } }
  35. #DV13 #FilthyRichAndroid Adapters and Stable IDs 22 public class StableArrayAdapter

    extends ArrayAdapter<String> { // ... other methods... @Override public long getItemId(int position) { String item = getItem(position); return mIdMap.get(item); } @Override public boolean hasStableIds() { return true; } }
  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; } }
  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); } });
  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; } }); }
  39. Circular Reveal!

  40. #DV13 #FilthyRichAndroid Circular reveal 27

  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
  42. #DV13 #FilthyRichAndroid Circular reveal 29 ALPHA_8 bitmap mask Bitmap texture

  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; }
  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(); } }
  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); }
  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); }
  47. #DV13 #FilthyRichAndroid Animating the spotlight 34

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

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

    scale up
  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);
  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();
  52. #DV13 #FilthyRichAndroid Android 4.4 Photo Editor 37

  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
  54. #DV13 #FilthyRichAndroid Google Now 39

  55. #DV13 #FilthyRichAndroid 40

  56. #DV13 #FilthyRichAndroid 40

  57. #DV13 #FilthyRichAndroid 40 No antialiasing!

  58. #DV13 #FilthyRichAndroid Path clipping 41 Path clip = new Path();

    clip.addCircle(x, y, radius, Path.Direction.CW); canvas.clipPath(clip); drawContent();
  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
  60. Activity Transitions!

  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
  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
  63. None
  64. #DV13 #FilthyRichAndroid Grayscale thumbnails 47 ColorMatrix grayMatrix = new ColorMatrix();

    grayMatrix.setSaturation(0); ColorMatrixColorFilter grayscaleFilter = new ColorMatrixColorFilter(grayMatrix); thumbnailDrawable.setColorFilter(grayscaleFilter);
  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(); } }
  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(); } }
  67. #DV13 #FilthyRichAndroid Transparent activity background 50 <activity ... android:theme="@style/Transparent" >

    </activity> <style name="Transparent"> <item name="android:windowNoTitle">true</item> <item name="android:windowIsTranslucent">true</item> <item name="android:windowBackground">@android:color/transparent</item> </style>
  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);
  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");
  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; } });
  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); } });
  72. #DV13 #FilthyRichAndroid Fade in black background 55 ObjectAnimator.ofInt(mBackground, "alpha", 0,

    255). start();
  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); }
  74. #DV13 #FilthyRichAndroid Animate drop shadow 57 ObjectAnimator shadowAnim = ObjectAnimator.ofFloat(

    mShadowLayout, "shadowDepth", 0, 1); shadowAnim.start();
  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); }
  76. Folding Layout!

  77. #DV13 #FilthyRichAndroid Fan Fare 60

  78. #DV13 #FilthyRichAndroid 61

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

  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(); }
  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(); }
  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(); }
  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(); }
  84. #DV13 #FilthyRichAndroid Color Filters • Can be used to modify

    a shader • Subclasses of ColorFilter - ColorMatrixColorFilter - LightingColorFilter - PorterDuffColorFilter 64
  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));
  86. Performance

  87. #DV13 #FilthyRichAndroid Smoother is Better • Consistent frame rate •

    Avoid hiccups • Avoid large steps over few frames 67
  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
  89. #DV13 #FilthyRichAndroid Avoid Overdraw • Developer options -> Show Overdraw

    • Window background vs. opaque containers vs. opaque views 69
  90. #DV13 #FilthyRichAndroid Get Off that UI Thread! • Avoid expensive

    operations on UI thread - network, database, bitmaps, ... • AsyncTask is your friend 70
  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
  92. #DV13 #FilthyRichAndroid clipPath • Not always the fastest way to

    clip to a path • Doesn’t support antialiasing • Try BitmapShader 72
  93. #DV13 #FilthyRichAndroid Consider Time Travel • Go see Android Performance

    Workshop 2 days ago - Memory - Performance tips - Tools - Case studies 73
  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
  95. #DV13 #FilthyRichAndroid Q&A 75 Filthy Rich Clients: Developing Animated and

    Graphical Effects