Fork me on GitHub

50 Android Hacks


Working your way around layouts

Centering views using weights

Using lazy loading and avoiding replication

<ViewStub
    android:id="@+id/map_stub"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:layout="@layout/map"
    android:inflatedId="@+id/map_view"/>

The inflatedId is the ID that the inflated view will have after we call inflate() or setVisibility() in the ViewStub class.If we want to get a reference to the view inflated, the inflate() method returns the view to avoid a second call to findViewById().

Creating a custom ViewGroup

Preferences hacks

Creating cool animations

Snappy transitions with TextSwitcher and ImageSwitcher

Adding eye candy to your ViewGroup’s children

Doing animations over the Canvas

Slideshow using the Ken Burns effect

View tips and tricks

Avoiding date validations with an EditText for dates

Formatting a TextView’s text

Adding text glowing effects

Rounded borders for backgrounds

<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
    <solid android:color="#AAAAAA"/>
    <corners android:radius="15dp"/>
</shape>

Getting the view’s width and height in the onCreate() method

详细介绍一下ViewTreeObserver这个类,这个类是用来注册当view tree全局状态改变时的回调监听器,这些全局事件包括很多,比如整个view tree视图的布局,视图绘制的开始,点击事件的改变等等。还有千万不要在应用程序中实例化ViewTreeObserver对象,因为该对象仅是由视图提供的。

ViewTreeObserver类提供了几个相关函数用来添加view tree的相关监听器:

public void addOnDrawListener (ViewTreeObserver.OnDrawListener listener),该函数为api 16版本中添加,作用是注册在该view tree将要绘制时候的回调监听器,注意该函数和相关的remove函数不能在监听器回调的onDraw()中调用。

public void addOnGlobalFocusChangeListener (ViewTreeObserver.OnGlobalFocusChangeListener listener),该函数用来注册在view tree焦点改变时候的回调监听器。

public void addOnGlobalLayoutListener (ViewTreeObserver.OnGlobalLayoutListener listener)该函数用来注册在该view tree中view的全局布局属性改变或者可见性改变时候的回调监听器。

public void addOnPreDrawListener (ViewTreeObserver.OnPreDrawListener listener)该函数用来注册当view tree将要被绘制时候(view 的 onDraw 函数之前)的回调监听器。

public void addOnScrollChangedListener (ViewTreeObserver.OnScrollChangedListener listener)该函数用来注册当view tree滑动时候的回调监听器,比如用来监听ScrollView的滑动状态。

public void addOnTouchModeChangeListener (ViewTreeObserver.OnTouchModeChangeListener listener)该函数用来注册当view tree的touch mode改变时的回调监听器,回调函数onTouchModeChanged (boolean isInTouchMode)中的isInTouchMode为该view tree的touch mode状态。

public void addOnWindowAttachListener (ViewTreeObserver.OnWindowAttachListener listener)api 18添加,该函数用来注册当view tree被附加到一个window上时的回调监听器。

public void addOnWindowFocusChangeListener (ViewTreeObserver.OnWindowFocusChangeListener listener)api 18添加,该函数用来注册当window中该view tree焦点改变时候的回调监听器。

而且对应每一个add方法都会有一个remove方法用来删除相应监听器。

match_parent: 直接放弃,无法measure出具体的宽/高。原因很简单,根据view的measure过程,构造此种MeasureSpec需要知道parentSize,即父容器的剩余空间,而这个时候我们无法知道parentSize的大小,所以理论上不可能测量处view的大小。

wrap_content:

int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);
v_view1.measure(widthMeasureSpec, heightMeasureSpec);

注意到(1<<30)-1,我们知道MeasureSpec的前2位为mode,后面30位为size,所以说我们使用最大size值去匹配该最大化模式,让view自己去计算需要的大小。

具体的数值(dp/px),这种模式下,只需要使用具体数值去measure即可,比如宽/高都是100px:

int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
v_view1.measure(widthMeasureSpec, heightMeasureSpec);

VideoViews and orientation changes

Removing the background to improve your Activity startup time

<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.NoBackground" parent="android:Theme"> 
    <item name="android:windowNoTitle">true</item> 
    <item name="android:windowBackground">@null</item>
</style>
</resources>

Removing the window background is a simple trick to gain some speed. The rule is simple: if the UI of your application is drawing 100% of the window contents, you should always set windowBackground to null. Remember that the theme can be set in an <application> or an <activity> tag.

Toast’s position hack

Tools

Removing log statements before releasing

Using the Hierarchy Viewer tool to remove unnecessary views

Patterns

The Model-View-Presenter pattern

BroadcastReceiver following Activity’s lifecycle

Architecture pattern using Android libraries

The SyncAdapter pattern

Working with lists and adapters

Handling empty lists

Creating fast adapters with a ViewHolder

Adding section headers to a ListView

Communicating with an Adapter using an Activity and a delegate

Taking advantage of ListView’s header

Handling orientation changes inside a ViewPager

ListView’s choiceMode

android:clickable="false"
android:focusable="false"
android:focusableInTouchMode="false"

Useful libraries

Aspect-oriented programming in Android

Empowering your application using Cocos2d-x

Interacting with other languages

Running Objective-C in Android

Using Scala inside Android

Ready-to-use snippets

Firing up multiple intents

Intent pickIntent = new Intent(Intent.ACTION_GET_CONTENT);
pickIntent.setType("image/*");

Intent takePhotoIntent;
takePhotoIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);

Intent chooserIntent = Intent.createChooser(pickIntent,getString(R.string.activity_main_pick_both));
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS,new Intent[]{takePhotoIntent});

startActivityForResult(chooserIntent, PICK_OR_TAKE_PICTURE);

Getting user information when receiving feedback

Adding an MP3 to the media ContentProvider

MediaUtils.saveRaw(this, R.raw.loop1, LOOP1_PATH);

ContentValues values = new ContentValues(5);
values.put(Media.ARTIST, "Android");
values.put(Media.ALBUM, "50AH");
values.put(Media.TITLE, "hack037");
values.put(Media.MIME_TYPE, "audio/mp3");
values.put(Media.DATA, LOOP1_PATH);

getContentResolver().insert(Media.EXTERNAL_CONTENT_URI, values);
MediaUtils.saveRaw(this, R.raw.loop2, LOOP2_PATH);

Uri uri = Uri.parse("file://" + LOOP2_PATH);
Intent i = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri);
sendBroadcast(i);

Adding a refresh action to the action bar

Getting dependencies from the market

Last-in-first-out image loading

public class LIFOTask extends FutureTask<Object>
  implements Comparable<LIFOTask> {
  private static long counter = 0;
  private final long priority;
  public LIFOTask(Runnable runnable) {
    super(runnable, new Object());
    priority = counter++;
}
  public long getPriority() {
    return priority;
}
  @Override
  public int compareTo(LIFOTask other) {
      return priority > other.getPriority() ? -1 : 1;
  }
}
public class LIFOThreadPoolProcessor {
  private BlockingQueue<Runnable> opsToRun =
  new PriorityBlockingQueue<Runnable>(64, new Comparator<Runnable>() {
    @Override
    public int compare(Runnable r0, Runnable r1) {
       if (r0 instanceof LIFOTask && r1 instanceof LIFOTask) {
          LIFOTask l0 = (LIFOTask)r0;
          LIFOTask l1 = (LIFOTask)r1;
          return l0.compareTo(l1);
        }
    return 0; 
    }
  });
  private ThreadPoolExecutor executor;
  public LIFOThreadPoolProcessor(int threadCount) {
    executor = new ThreadPoolExecutor(threadCount, threadCount, 0,
      TimeUnit.SECONDS, opsToRun);
  }
  public Future<?> submitTask(LIFOTask task) {
    return executor.submit(task);
  }
  public void clear() {
    executor.purge();
  } 
}

Beyond database basics

Building databases with ORMLite

Creating custom functions in SQLite

Batching database operations

public ContentProviderResult[] applyBatch(
            ArrayList<ContentProviderOperation> operations);

Avoiding fragmentation

Handling lights-out mode

Using new APIs in older devices

Using new APIs in older devices

Backward-compatible notifications

Creating tabs with fragments

Building tools

Handling dependencies with Apache Maven

Installing dependencies in a rooted device

Using Jenkins to deal with device diversity

references to read