android ui tips & tricks
DESCRIPTION
Shem will share development tips while explaining how things work under-the-hood. As part of his talk, he will demonstrate the right way of working with images, custom views, ListViews and Animations, with an emphasis of how to make your app feel slick and fast on all Android devices.TRANSCRIPT
Android UI Tips & Tricks
Shem Magnezi
Making your good app great
Know your app
Understand what you
need
No magic/ generic
solutions
You know how to write a good
app
Looks good Feel slick
Not gonna talk about
viral/ design/ downloads
etc...
Agenda
● Working with images● Bring your views into life with
animations● Upgrade your lists views
Working with images
Images in memory
Bitmap memory size:Bitmap.getWidth() * Bitmap.getHeight() * Bitmap.config
Determine how much the bitmap gonna take:Image.width * Image.height * Options.config / Options.sampleSize
Cache Cache Cache
● Use cache for performance● Be careful not using too much memory
Determine your cache size
● Approximate per-application memory: getSystemService(Context.ACTIVITY_SERVICE).getMemoryClass()
● Pay attention to: onTrimMemory(int level) on your Application
● Profile your memory usage live
Loading the proper image type
For small image views work with thumbnails:MediaStore.Images.Thumbnails.getThumbnail(
..., int kind, ...)
MINI_KIND: 512 x 384
MICRO_KIND: 96 x 96
Sample size
Original size is probably too big, so load smaller size.
Original
inSampleSize=2
memory/4
Determine the right sample size
● Get the original image size using: options.inJustDecodeBounds = true;
● Get the view that gonna present the image● Find the minimum sample_size so:
o image.width / sample_size > view.width o image.height / sample_size > view.heighto it also prefer that sample_size will be power of 2 for
faster/ easier decoding
Find your view size
Sometimes your view size is 0 (cause is not yet drawn), so you should wait until the system will draw it:
view.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
public void onGlobalLayout() {//load image for the right size
}});
}
Determine image sizeCENTER_CROP
float scale = Math.max(viewW / imgW, viewH / imgH)
float scaledWidth = scaale * viewW;
float scaledHeight = scale * viewHt;
CENTER_INSIDE
float scale = Math.min(viewW / imgW, viewH / imgH)
float scaledWidth = scaale * viewW;
float scaledHeight = scale * viewHt;
Make your cache a bit smarterget(Item image, Size size) {
cached = getImage(image);if (cached != null) {
if (cached.size >= size) {
//saved time!
} else {//maybe
display a lower //resolution until loading
//the full image}
} else {//photo not in cache,
but we//did our best}}
put(Item image, Size size) {if (size < MICRO_KIND_SIZE) {
//load micro kind thumbnail
} else if (size < MINI_KIND_SIZE) {
//load mini kind thumbnail
} else {//read the full image
with the//right sample size
}}
Bitmap config
ARG_565 has no alpha channel and is it in lower quality
But:● It’s ~x2 faster to load● It consume half of the
memory● Most of the time you
won’t see any differencesource: Romain Guy
Animations
Interpolator
Sometimes you can use interpolator instead of couple of sequenced animations.For example, the built-in bounce animation on android.
Between-activities animationsSet activity style:<item name="android:windowBackground">@android:color/transparent</item>
When moving to this activity:startActivity(intent);overridePendingTransition(0, 0);
Do the animation:ViewTreeObserver observer = animationImage.getViewTreeObserver();observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
public boolean onPreDraw() {animationImage.getViewTreeObserver().removeOnPreDrawListener(this); runEnterAnimation(back, startBounds);}});
Pre drawer listener?
Very useful for animations!● Create enter animation to your activity● Create animation to your view when
it’s added to the screen● Animate list items when the list
changes
Smart image animation
Case #1Scaling animation for the image view:ObjectAnimator.ofFloat(image_1, "scaleY", 1, 1.5f);
<ImageViewandroid:id="@+id/image_1" android:layout_width="225dp"android:layout_height="150dp"android:scaleType="centerCrop"/>
When animating the view there is no re-layout and the image not preserving it’s scale type
Case #2Use a frame and do a reverse scaling to the inner image:<RelativeLayout android:id="@+id/image_2"
android:layout_width="225dp"android:layout_height="150dp"> <ImageView android:id="@+id/inner_image"android:layout_width="225dp"android:layout_height="300dp"android:layout_marginTop="-75dp" android:layout_marginBottom="-
75dp"/></RelativeLayout>
anim.playTogether(ObjectAnimator.ofFloat(image_2, "scaleY", 1,
1.5f), ObjectAnimator.ofFloat(inner, "scaleY", 1f, 0.6666f));
● Lots of calculations
● The animation is not linear
● Need an extra view
Case #3Use an extra image as the target view:anim.playTogether(ObjectAnimator.ofFloat(image_3, "scaleY", 0.6666f, 1f),ObjectAnimator.ofFloat(image_3, "alpha", 0, 1));
<ImageViewandroid:id="@+id/image_3" android:layout_width="225dp"android:layout_height="225dp"android:scaleType="centerCrop"/>
● You are losing the original view
● The animation isn’t smooth
Case #4Implement you own Animation:public class ExpandAnimation extends Animation {
protected void applyTransformation(float inp ...) {...if (inp < 1.0f) {
lp.height =(int)(start + (end - start)* inp);mAnimatedView.requestLayout();
}}
<ImageView android:id="@+id/image_4"android:layout_width="225dp"
android:layout_height="150dp" android:scaleType="centerCrop" />
Working with Lists
The basic things
● Reuse items● ViewHolder pattern● Long tasks should run in background
with AsyncTask● Cancel view loading tasks using
RecyclerListener
● Use ListFragment
Profile your drawing
● Design your layout as flat as you can● Avoid over drawing or nested weights● Profile your list using GPU overdraw
and GPU Rendering in developer options
Empty view in your ListFragment
Use android:id="@android:id/empty"
for the case the list is empty
<ListView android:id="@android:id/list" … /><RelativeLayout android:id="@android:id/empty" ... />
Save each item state
In your adapter:Set<Integer> opened = HashSet<Integer>();
On widget opened:opened.add(item.getId());
In getView():view.setOpened(opened.contains(item.getId());
Scrolled view inside ListViewYou sometime want to put a view that can be scrolled by himself as one of your listview items- for example putting a grid view of images.For doing it you must let layout manager that this view must take it’s full size:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int heightSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, heightSpec);
getLayoutParams().height = getMeasuredHeight();
}
source: stackoverflow.com
Smart scrollbar for easy navigation
● Put a relative view that contains your list view and set him as a OnScrollListener
● On onScroll calc the right position of your scroller view using totalItemCount and visibleItemCount
● On draw put your scroller view using setTranslationY
Smart scrollbar for easy navigationYou can even add a behavior for dragging the scroller using onTouchEvent:public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {if (//event in scroll bar view)
mDragging = true;} else if (me.getAction() == MotionEvent.ACTION_UP) {
if (mDragging) mDragging = false;} else if (me.getAction() == MotionEvent.ACTION_MOVE) {
if (mDragging)mList.setSelectionFromTop(//calc the right item index, 0);}}
</presentation>
Thank you.