Android数据结构优化教程

ArrayList与LinkedList

  • ArrayList查找快,增删慢,内部为数组,连续空间,地址带顺序查找修改快,增加,删除底层为System.copy操作,而copy为循环赋值,末尾添加删除不受影响。
  • LinkedList增删快,查找慢,内部操作node,是链表,插入删除只操作该节点的头尾指针即可,内存不连续,查找是轮询的方式,使用的for循环耗时操作。查找修改慢

选择方式:数据不进行大量增删,只按顺序排列显示用ArrayList如listview,recycleview;显示的数据包含用户可以进行删除操作,使用LinkedList;

HashMap:1.7之前Android24之前使用的数组保存,数组的构造使用的链表,数组(ArrayList) + 链表,整体为一个数组,数组内每一个元素,为链表,数组和链表共同构成一个节点;1.8之后,除了数组和链表外还有红黑树(二叉树,平衡二叉树),LinkedHaspMap双向指针。

1.7:

transient Entry[] table = (Entry<K,V>[]) EMPTY_TABLE;

如何保证K:V唯一对应,查看put()方法

public V put(K key, V value) {
    if (table == EMPTY_TABLE) {
        inflateTable(threshold);
    }
    if (key == null)
        return putForNullKey(value);
    int hash = hash(key);
    int i = indexFor(hash, table.length);
    for (Entry<K, V> e = table[i]; e != null; e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }
    modCount++;
    addEntry(hash, key, value, i);
    return null;
}
final int hash(Object k) {
        int h = hashSeed;
        if (0 != h && k instanceof String) {
            return sun.misc.Hashing.stringHash32((String) k);
        }
        h ^= k.hashCode();
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }
    static int indexFor(int h, int length) {
        return h & (length-1);
    }
void addEntry(int hash, K key, V value, int bucketIndex) {
    	//sizi大于阈值 2倍扩容
        if ((size >= threshold) && (null != table[bucketIndex])) {
            resize(2 * table.length);
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }
        createEntry(hash, key, value, bucketIndex);
    }
void createEntry(int hash, K key, V value, int bucketIndex) {
        Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        size++;
    }

put过程:k为object类型,根据k找到int类型的hashcode,装箱过程,table []大小未知,根据hashcode % table的length求模运算,得到范围0-length-1,求模运算等价于位运算,源码中使用的是位运算,更快,往JVM转为字节码时速度更快,这时得到下标index即源码中的i,通过下标i找到要操作的位置,完成k的任务。然后进行 addEntry(hash, key, value, i);调用了createEntry方法,先把下标i记录成e,然后使用HashMapEntry赋值,new一个新的节点,新节点指向e,再把新节点赋值给table[bucketIndex]即头插法,将新节点放到i的位置。

put: k:Object -> hashcode :int -> (hashcode % length) == (h & (length -1))-> :0~length-1 index;

哈希碰撞:得到index的过程是hash运算的位移运算(求模),求模是多对一的过程,多对一的过程,hashcode1 hashcode2 会得到相同的index,于是出现了哈希碰撞(哈希冲突),hashmap提供了解决碰撞的方法:链表法,将新加入的的节点作为下一个节点的next节点。

cpu:所有操作都是位运算,其中最快的就是 位置,&或|运算而不是+

查看get()方法

public V get(Object key) {
    if (key == null)
        return getForNullKey();
    int hash = hash(key.hashCode());
    for (Entry<K,V> e = table[indexFor(hash, table.length)];
        e != null;
        e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
            return e.value;
    }
    return null;
}

类似get()先找到index ,然后轮询table[]这个位置的链表。

扩容问题:

加载因子:final float loadFactor = 0.75;(这个表超过百分之多少开始扩容)

阈值: 0.75f*16(length)=12;element(所有存的元素)>12即扩容,

默认hashmap大小:16,即new Hashmap()内的table[16],大小需为2的次幂

扩容的意义:0.6-0.75做加载因子最合适,数学家测试的结果。提升效率,当一个表全部冲突的时候效率最低退化成单链表,增删高效,查找低。避免冲突,长度更大,冲突的可能性更低

addEntry时如果大于阈值2倍扩容,调用resize()

void resize(int newCapacity) {
    Entry[] oldTable = table;
    int oldCapacity = oldTable.length;
    if (oldCapacity == MAXIMUM_CAPACITY) {
        threshold = Integer.MAX_VALUE;
        return;
    }
    Entry[] newTable = new Entry[newCapacity];
    transfer(newTable);//用来将原先table的元素全部移到newTable里面
    table = newTable;  //再将newTable赋值给table
    threshold = (int)(newCapacity * loadFactor);//重新计算临界值
}

扩容的时候对hasp表进行转移transfer

void transfer(Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;
        //遍历旧表
        for (Entry<K,V> e : table) {
            //将所有的节点进行hash运算
            while(null != e) {
                Entry<K,V> next = e.next;
                if (rehash) {
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                int i = indexFor(e.hash, newCapacity);
                e.next = newTable[i];
                newTable[i] = e;
                e = next;
            }
        }
    }

扩容耗性能,避免扩容,创建时应该评估hash表的大小,(大小/0.75+1),如何保证大小为2的次幂,这就和put时有关。表空时调用inflateTable(threshold);

private void inflateTable(int toSize) {
    // Find a power of 2 >= toSize
    int capacity = roundUpToPowerOf2(toSize);
    threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
    table = new Entry[capacity];
    initHashSeedAsNeeded(capacity);//初始化hashSeed变量
}
private static int roundUpToPowerOf2(int number) {
    // assert number >= 0 : "number must be non-negative";
    return number >= MAXIMUM_CAPACITY
        ? MAXIMUM_CAPACITY
        : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
}

会进行运算,转为最近的2的次幂。hash表真正的初始化是在put的时候,而不是new的时候。

初始化:put时候,防止创建未用,再put时,才真正初始化

大小为2的次幂原因:保证h & (length -1)运算,如 16-1的二进制位:1111,32-1为:11111,如果不是2的次幂,如length为10,length-1为9,二进制为:1001,进行与运算后只有最高位和最低位起作用,2的次幂的话,起作用的值更多,碰撞的可能性更低

例子:

十进制 二进制(hash值) 与运算
h1 6 0110
length1-1 9 1001 0000
length2-1 15 1111 0110
h2 7 0111
length1-1 9 1001 0001
length2-1 15 1111 0111

hashmap有阈值,25%的内存浪费 空间换时间,尤其扩容的时候,如果多1个节点就扩容了两倍。

安卓中出现了SparseArray:使用双数组,一个存key 一个存value

public class SparseArray<E> implements Cloneable {
    private static final Object DELETED = new Object();
    private boolean mGarbage = false;
    private int[] mKeys;
    private Object[] mValues;
    private int mSize;

key为int类型,value对object

public void put(int key, E value) {
    int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
    //原来已经有key,可能是remove后,value存放着DELETED,也可能是存放旧值,那么就替换
    if (i >= 0) {
        mValues[i] = value;
    } else {
        //没有找到,对i取反,得到i= lo(ContainerHelpers.binarySearch)
        i = ~i;
        //如果i小于数组长度,且mValues==DELETED(i对应的Key被延迟删除了)
        if (i < mSize && mValues[i] == DELETED) {
            //直接取代,实现真实删除原键值对
            mKeys[i] = key;
            mValues[i] = value;
            return;
        }
        //数组中可能存在延迟删除元素且当前数组长度满,无法添加
        if (mGarbage && mSize >= mKeys.length) {
            //真实删除,将所有延迟删除的元素从数组中清除;
            gc();
            //清除后重新确定当前key在数组中的目标位置;
            // Search again because indices may have changed.
            i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
        }
        //不存在垃圾或者当前数组仍然可以继续添加元素,不需要扩容,则将i之后的元素全部后移,数组中仍然存在被DELETED的垃圾key;
        mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
        mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
        //新元素添加成功,潜在可用元素数量+1
        mSize++;
    }
}

class ContainerHelpers {

// This is Arrays.binarySearch(), but doesn't do any argument validation.
//第一个参数array为keys的数组,第二个为数组中元素个数(与keys的length不一定相等),第三个value为目标的key
static int binarySearch(int[] array, int size, int value) {
    //lo为二分查找的左边界
    int lo = 0;
    //hi为二分查找的右边界
    int hi = size - 1;
    //还没找到,继续查找
    while (lo <= hi) {
        //左边界+右边界处以2,获取到mid 的index
        final int mid = (lo + hi) >>> 1;
        //获取中间元素
        final int midVal = array[mid];
        // 目标key在右部分  。。。。感觉这部分太简单了
        if (midVal < value) {
            lo = mid + 1;
        } else if (midVal > value) {
            hi = mid - 1;
        } else {
            //相等,找到了,返回key对应在array的下标;
            return mid;  // value found
        }
    }
    //没有找到该元素,对lo取反!!!!!很重要
    return ~lo;  // value not present
}

寻找key使用二分查找,找到该插入的index后,后续的元素使用arraycopy。

内存节约,速度不会慢,使用的二分查找,一个一个放for循环放入,乱序二分。越用越快,remove的时候,把移除的下标标记为delet,下次插入到这里,直接放,不要数组位移。空间复用,效率更高。

public void delete(int key) {
    //查找对应key在数组中的下标,如果存在,返回下标,不存在,返回下标的取反;
    int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
    //key存在于mKeys数组中,将元素删除,用DELETED替换原value,起标记作用;
    if (i >= 0) {
        if (mValues[i] != DELETED) {
            mValues[i] = DELETED;
            mGarbage = true;
        }
    }
}
/**
 * @hide
 * Removes the mapping from the specified key, if there was any, returning the old value.
 */
public E removeReturnOld(int key) {
    int i = ContainerHelpers.binarySearch(mKeys, mSize, key);

    if (i >= 0) {
        if (mValues[i] != DELETED) {
            final E old = (E) mValues[i];
            mValues[i] = DELETED;
            mGarbage = true;
            return old;
        }
    }
    return null;
}
/**
 * Alias for {@link #delete(int)}.
 */
public void remove(int key) {
    delete(key);
}

缺点:key只能是int值

ArrayMap:HashMap + SparseArray思想

@Override
    public V put(K key, V value) {
        //当前容量
        final int osize = mSize;
        //key的散列值
        final int hash;
        //key的hash所在的下标
        int index;
        if (key == null) {
            //key为空hash值为0
            hash = 0;
            //找到key的hash值的下标
            index = indexOfNull();
        } else {
            //key的hash值
            hash = mIdentityHashCode ? System.identityHashCode(key) : key.hashCode();
            // 找到key的hash值的下标
            index = indexOf(key, hash);
        }
        if (index >= 0) {
            //当前要添加的元素已经存在,则直接进行替换操作
            index = (index<<1) + 1;
            final V old = (V)mArray[index];
            mArray[index] = value;
            return old;
        }
        //取反得到要添加元素的位置
        index = ~index;
        if (osize >= mHashes.length) {
            //扩容新的容量
            final int n = osize >= (BASE_SIZE*2) ? (osize+(osize>>1))
                    : (osize >= BASE_SIZE ? (BASE_SIZE*2) : BASE_SIZE);
            if (DEBUG) Log.d(TAG, "put: grow from " + mHashes.length + " to " + n);
            //原hash数组
            final int[] ohashes = mHashes;
            //原散列表
            final Object[] oarray = mArray;
            //扩容操作
            allocArrays(n);
            if (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) {
                throw new ConcurrentModificationException();
            }
            if (mHashes.length > 0) {
                if (DEBUG) Log.d(TAG, "put: copy 0-" + osize + " to 0");
                //将原数组中的拷贝回新数组中
                System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length);
                System.arraycopy(oarray, 0, mArray, 0, oarray.length);
            }
            //回收释放操作
            freeArrays(ohashes, oarray, osize);
        }
        if (index < osize) {
            if (DEBUG) Log.d(TAG, "put: move " + index + "-" + (osize-index)
                    + " to " + (index+1));
            //将index处(含index)及其之后的数据往后移
            System.arraycopy(mHashes, index, mHashes, index + 1, osize - index);
            System.arraycopy(mArray, index << 1, mArray, (index + 1) << 1, (mSize - index) << 1);
        }
        if (CONCURRENT_MODIFICATION_EXCEPTIONS) {
            if (osize != mSize || index >= mHashes.length) {
                throw new ConcurrentModificationException();
            }
        }
        //将数据添加到index处
        mHashes[index] = hash;
        mArray[index<<1] = key;
        mArray[(index<<1)+1] = value;
        mSize++;
        return null;
    }
private void allocArrays(final int size) {
        if (mHashes == EMPTY_IMMUTABLE_INTS) {
            //扩容时如果mHashes 是不可变的,则抛出异常
            throw new UnsupportedOperationException("ArrayMap is immutable");
        }
        if (size == (BASE_SIZE*2)) {
            //如果扩容容量为8(BASE_SIZE=4)
            synchronized (ArrayMap.class) {
                if (mTwiceBaseCache != null) {
                    /**
                      如果当前有容量为8的int缓存可复用数组和容量为16的object缓存可复用数组,则复用这些数组,而不重新new
                     */
                    final Object[] array = mTwiceBaseCache;
                    mArray = array;
                    mTwiceBaseCache = (Object[])array[0];
                    mHashes = (int[])array[1];
                    array[0] = array[1] = null;
                    mTwiceBaseCacheSize--;
                    if (DEBUG) Log.d(TAG, "Retrieving 2x cache " + mHashes
                            + " now have " + mTwiceBaseCacheSize + " entries");
                    return;
                }
            }
        } else if (size == BASE_SIZE) {
             //如果扩容容量为4(BASE_SIZE=4)
            synchronized (ArrayMap.class) {
                if (mBaseCache != null) {
                    /**
                      如果当前有容量为4的int缓存可复用数组和容量为8的object缓存可复用数组,则复用这些数组,而不重新new
                     */
                    final Object[] array = mBaseCache;
                    mArray = array;
                    mBaseCache = (Object[])array[0];
                    mHashes = (int[])array[1];
                    array[0] = array[1] = null;
                    mBaseCacheSize--;
                    if (DEBUG) Log.d(TAG, "Retrieving 1x cache " + mHashes
                            + " now have " + mBaseCacheSize + " entries");
                    return;
                }
            }
        }
        mHashes = new int[size];
        mArray = new Object[size<<1];
    }
    private static void freeArrays(final int[] hashes, final Object[] array, final int size) {
        if (hashes.length == (BASE_SIZE*2)) {
            //如果当前容量为8(BASE_SIZE=4)
            synchronized (ArrayMap.class) {
                if (mTwiceBaseCacheSize < CACHE_SIZE) {
                    //缓存当前数组,并将数组下标为2之后的数据设置为null
                    array[0] = mTwiceBaseCache;
                    array[1] = hashes;
                    for (int i=(size<<1)-1; i>=2; i--) {
                        array[i] = null;
                    }
                    mTwiceBaseCache = array;
                    mTwiceBaseCacheSize++;
                    if (DEBUG) Log.d(TAG, "Storing 2x cache " + array
                            + " now have " + mTwiceBaseCacheSize + " entries");
                }
            }
        } else if (hashes.length == BASE_SIZE) {
            //如果当前容量为4(BASE_SIZE=4)
            synchronized (ArrayMap.class) {
                //缓存当前数组,并将数组下标为2之后的数据设置为null
                if (mBaseCacheSize < CACHE_SIZE) {
                    array[0] = mBaseCache;
                    array[1] = hashes;
                    for (int i=(size<<1)-1; i>=2; i--) {
                        array[i] = null;
                    }
                    mBaseCache = array;
                    mBaseCacheSize++;
                    if (DEBUG) Log.d(TAG, "Storing 1x cache " + array
                            + " now have " + mBaseCacheSize + " entries");
                }
            }
        }
    }
 @Override
  public V get(Object key) {
        //取得key的hashcode所在mHashes的下标,
        final int index = indexOfKey(key);
        /**
        根据mArray的数据存储结构,得知mHashes的 下标*2  (index << 1)便得到其对应元素在mArray的起始下标,第一个是key,第二个是value
        */
        return index >= 0 ? (V)mArray[(index<<1)+1] : null;
  }
public int indexOfKey(Object key) {
        return key == null ? indexOfNull()
                : indexOf(key, mIdentityHashCode ? System.identityHashCode(key) : key.hashCode());
}
int indexOf(Object key, int hash) {
        final int N = mSize;
        // Important fast case: if nothing is in here, nothing to look for.
        if (N == 0) {
            return ~0;
        }
        //二分法找到hash所在的下标
        int index = binarySearchHashes(mHashes, N, hash);
        // If the hash code wasn't found, then we have no entry for this key.
        if (index < 0) {
            //没找到,直接返回
            return index;
        }
        // If the key at the returned index matches, that's what we want.
        if (key.equals(mArray[index<<1])) {
            //如果hash下标对应mArray中的key与要找的key相等,直接返回当前下标
            return index;
        }
        //出现冲突处理方案
        //遍历当前index之后元素,找到匹配的key所在的下标
        // Search for a matching key after the index.
        int end;
        for (end = index + 1; end < N && mHashes[end] == hash; end++) {
            if (key.equals(mArray[end << 1])) return end;
        }
        //遍历当前index之前元素,找到匹配的key所在的下标
        // Search for a matching key before the index.
        for (int i = index - 1; i >= 0 && mHashes[i] == hash; i--) {
            if (key.equals(mArray[i << 1])) return i;
        }
        // Key not found -- return negative value indicating where a
        // new entry for this key should go.  We use the end of the
        // hash chain to reduce the number of array entries that will
        // need to be copied when inserting.
        return ~end;
}

遇到hash冲突使用追加的方式,冲突时候index累加的方式。

性能提升: 空间和时间的选择问题

到此这篇关于Android数据结构优化教程的文章就介绍到这了,更多相关Android数据结构内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Android 数据结构全面总结分析

    前言 这次算一个总结,我们平时都会用到各种各样的数据结构,但是可能从未看过它们内部是如何去实现的.只有了解了它们内部大概的一个实现原理,才能在不同的场景中能选出最适合这个场景的数据结构. 虽然标题说是Android,但其实有一半是属于java的,由于涉及得比较多,所以打算分篇来写会比较好,我不会把全部的源码都进行分析,主要做的是分析一些能表现这些数据结构的特征. Collection 一切数据结构的最顶层,是一个接口,继承迭代器Iterable,主要是定义所有数据结构的公共行为,比如说boole

  • Android开发数据结构算法ArrayList源码详解

    目录 简介 ArrayList源码讲解 初始化 扩容 增加元素 一个元素 一堆元素 删除元素 一个元素 一堆元素 修改元素 查询元素 总结 ArrayList优点 ArrayList的缺点 简介 ArrayList是List接口的一个实现类,它是一个集合容器,我们通常会通过指定泛型来存储同一类数据,ArrayList默认容器大小为10,自身可以自动扩容,当容量不足时,扩大为原来的1.5倍,和上篇文章的Vector的最大区别应该就是线程安全了,ArrayList不能保证线程安全,但我们也可以通过其

  • Android Map数据结构全面总结分析

    目录 前言 Map ArrayMap TreeMap HashMap 总结 前言 上一篇讲了Collection.Queue和Deque.List或Set,没看的朋友可以去简单看看,这一篇主要讲和Map相关的数据结构. Map 上篇有介绍过,Map是另一种数据结构,它独立于Collection,它的是一个接口,它的抽象实现是AbstractMap,它内部是会通过Set来实现迭代器 Set<Map.Entry<K, V>> entrySet(); 是和Set有关联的,思想上主要以ke

  • Android Studio使用教程(五):Gradle命令详解和导入第三方包

    Android Studio + Gradle的组合用起来非常方便,很多第三方开源项目也早都迁移到了Studio,为此今天就来介绍下查看.编译并导入第三方开源项目的方法. Sublime + Terminal编译并查看源码 首先来给大家介绍一种简便并且个人最喜欢的一种办法.很多时候我们在GitHub上看到一个不错的开源项目,一般有两种需求,阅读源码和查看运行效果,如果是单纯的查看源码我更喜欢用一些轻量级编辑器,如vim,sublime等,vim不是很熟练,所以个人一种都习惯用sublime来查看

  • Android性能优化以及数据优化方法

    Android性能优化-布局优化 今天,继续Android性能优化 一 编码细节优化. 编码细节,对于程序的运行效率也是有很多的影响的.今天这篇主题由于技术能力有限,所以也不敢在深层去和大家分享.我将这篇主题分为以下几个小节: (1)缓存 (2)数据 (3)延迟加载和优先加载 1> 缓存 在Android中缓存可以用在很多的地方:对象.IO.网络.DB等等..对象缓存能减少内存分配,IO缓存能对磁盘的读写访问,网络缓存能减少对网络的访问,DB缓存能减少对数据库的操作. 缓存针对的场景在Andro

  • Android性能优化方法

    GPU过度绘制 •打开开发者选型,"调试GPU过度绘制",蓝.绿.粉红.红,过度绘制依次加深  •粉红色尽量优化,界面尽量保持蓝绿颜色  •红色肯定是有问题的,不能忍受 使用HierarchyView分析布局层级 •删除多个全屏背景:应用中不可见的背景,将其删除掉  •优化ImageView:对于先绘制了一个背景,然后在其上绘制了图片的,9-patch格式的背景图中间拉伸部分设置为透明的,Android 2D渲染引擎会优化9-patch图中的透明像素.这个简单的修改可以消除头像上的过度

  • 详解Android内存优化策略

    目录 前言 一.内存优化策略 二.具体优化的点 1.避免内存泄漏 2.Bitmap等大对象的优化策略 (1) 优化Bitmap分辨率 (2) 优化单个像素点内存 (3) Bitmap的缓存策略 (4) drawable资源选择合适的drawable文件夹存放 (5) 其他大对象的优化 (6) 避免内存抖动 3.原生API回调释放内存 4.内存排查工具 (1)LeakCanary监测内存泄漏 (2)通过Proflier监控内存 (3)通过MAT工具排查内存泄漏 总结 前言 在开始之前需要先搞明白一

  • Android 内存优化知识点梳理总结

    目录 RAM 和 ROM 常见内存问题 内存溢出 内存泄漏 常见内存泄漏场景 静态变量或单例持有对象 非静态内部类的实例生命周期比外部类更长导致的内存泄漏 Handler 导致的内存泄漏 postDelayed 导致的内存泄漏 View 的生命周期大于 Activity 时导致的内存泄漏 集合中的对象未释放导致内存泄漏 WebView 导致的内存泄漏 内存抖动 解决方案 其他优化点 App 内存过低时主动清理 前言: Android 操作系统给每个进程都会分配指定额度的内存空间,App 使用内存

  • Android 性能优化实现全量编译提速的黑科技

    目录 一.背景描述 二.效果展示 2.1.测试项目介绍 三.思路问题分析与模块搭建: 3.1.思路问题分析 3.2.模块搭建 四.问题解决与实 编译流程启动,需要找到哪一个 module做了修改 module 依赖关系获取 module 依赖关系 project 替换成 aar 技术方案 hook 编译流程 五.一天一个小惊喜( bug 较多) 5.1 output 没有打包出 aar 5.2 发现运行起来后存在多个 jar 包重复问题 5.3 发现 aar/jar 存在多种依赖方式 5.4 发

  • Android高手进阶教程(二十六)之---Android超仿Path菜单的功能实现!

    Hi~大家好,出来创业快3个月了,一切还不错,前一段时间用了业余时间搞了个问答类网站YQMA.想做中国的stackoverflow,哈哈,只是YY下,希望大家多多支持! 好了,今天给大家分享的是Path菜单的简单实现,可以支持自定义方向(左上,右上,右下,左下),并且可以自定义菜单的个数,难点就是菜单的摆放位置(动态设置margin),还有动画的实现,其实动画只是简单用了个TranslateAnimation,N个菜单一起移动的时候感觉很cool~ 这里也用到了自定义标签,这里不懂的童鞋可以看我

  • Android Studio使用教程(四):Gradle基础

    其实很早之前也写了一篇Gradle的基础博客,但是时间很久了,现在Gradle已经更新了很多,所以暂且结合Stduio 1.0正式版与最新的Gradle语法来详细讲解下,小伙伴们直接跟我一步步来学习吧. 什么是Gradle? Gradle是一种依赖管理工具,基于Groovy语言,面向Java应用为主,它抛弃了基于XML的各种繁琐配置,取而代之的是一种基于Groovy的内部领域特定(DSL)语言. 安装Gradle 在Android Studio系列教程一–下载与安装中新建项目成功后会下载Grad

  • Android Studio使用教程(三):常用快捷键

    Android Studio 1.0正式版发布啦 今天是个大日子,Android Studio 1.0 终于发布了正式版, 这对于Android开发者来说简直是喜大普奔的大消息啊,那么就果断来下载使用. 官方下载地址: http://developer.android.com/sdk/index.html 如果你之前已经使用其他版本的Studio,那么直接覆盖就好了,如果是第一次使用,那么参照Android Studio系列教程一进行安装配置. 于此同时一起更新的还有SDK Tools等,打开S

随机推荐