《Android基于PinnedSectionListView实现联系人通讯录》

我在之前的文章中写过文章,介绍过PinnedSectionListView(文章地址链接: http://blog.csdn.net/zhangphil/article/details/47144125 )【文1】,也有一篇文章是关于Android通讯录联系人操作的基础知识(文章地址链接: http://blog.csdn.net/zhangphil/article/details/47250747 )【文2】。【文1】和【文2】作为进一步做Android联系人通讯录的准备工作。
现在基于PinnedSectionListView,实现一个目前较为流行的、能够说明Android通讯录联系人的大体模型外架。

如图:


测试用的MainActivity.java :

package zhangphil.contacts;import java.util.ArrayList;import com.hb.views.PinnedSectionListView;import com.hb.views.PinnedSectionListView.PinnedSectionListAdapter;import android.app.ListActivity;import android.content.ContentResolver;import android.content.Context;import android.content.Intent;import android.database.Cursor;import android.graphics.Color;import android.net.Uri;import android.os.Bundle;import android.provider.ContactsContract;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.AdapterView;import android.widget.ArrayAdapter;import android.widget.ListView;import android.widget.TextView;public class MainActivity extends ListActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);ArrayList<Contact> contacts = new ArrayList<Contact>();// 把联系人说装载到contacts中。readContacts(contacts);final ArrayList<Item> items = new ArrayList<Item>();// 从字母A开始到Z。26个字母,遍历联系人中的首字符是否相等。// 相等则归入一组。int A = (int) 'A';for (int i = 0; i < 26; i++) {int letter = A + i;char c = (char) letter;Item group = new Item();group.type = Item.GROUP;group.text = c + "";items.add(group);for (int j = 0; j < contacts.size(); j++) {Contact cc = contacts.get(j);String ss = cc.firstLetterOfName();if (ss.equals(group.text)) {Item child = new Item();child.type = Item.CHILD;child.text = cc.name + " " + cc.getPhoneNumbers();child.contact = cc;items.add(child);}}}PinnedSectionListView listView = (PinnedSectionListView) this.getListView();listView.setAdapter(new MyAdapter(this, -1, items));// 增加点击事件,当用户点击ListView的item时候,打电话。listView.setOnItemClickListener(new ListView.OnItemClickListener() {@Overridepublic void onItemClick(AdapterView<?> arg0, View arg1, int pos,long arg3) {Item item = items.get(pos);Contact contact = item.contact;// 简单演示期间,我们只选择第一个电话(不管有几个号码)。String number = contact.phoneNumbers.get(0);// Android拨打电话Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse("tel:"+ number));startActivity(intent);}});}// 用于承载数据块的类。// 字段分为类型(type)和值(text)。private class Item {public static final int GROUP = 0;public static final int CHILD = 1;public int type;public String text;public Contact contact = null;}private class MyAdapter extends ArrayAdapter<Item> implementsPinnedSectionListAdapter {private LayoutInflater inflater = null;private ArrayList<Item> items;public MyAdapter(Context context, int resource, ArrayList<Item> items) {super(context, resource);inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);this.items = items;}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {int type = this.getItemViewType(position);Item item = getItem(position);String text = item.text;// 以下是根据不同的类型,加载不同的布局。用于显示不同的数据类型。// 标签和联系人详情。if (type == Item.GROUP) {convertView = inflater.inflate(android.R.layout.simple_list_item_1, null);TextView tv = (TextView) convertView.findViewById(android.R.id.text1);tv.setText(text);tv.setBackgroundColor(Color.RED);// 可以将背景设置为半透明,更好的观察效果。// tv.getBackground().setAlpha(128);}if (type == Item.CHILD) {convertView = inflater.inflate(android.R.layout.simple_list_item_1, null);TextView tv = (TextView) convertView.findViewById(android.R.id.text1);tv.setText(text);}return convertView;}@Overridepublic int getItemViewType(int position) {return getItem(position).type;}// 2个type:GROUP或者CHILD。@Overridepublic int getViewTypeCount() {return 2;}@Overridepublic Item getItem(int position) {return items.get(position);}@Overridepublic int getCount() {return items.size();}// 假设此方法返回皆为false。那么PinnedSectionListView将退化成为一个基础的ListView.// 只不过退化后的ListView只是一个拥有两个View Type的ListView。// 从某种角度上讲,此方法对于PinnedSectionListView至关重要,因为返回值true或false,将直接导致PinnedSectionListView是一个PinnedSectionListView,还是一个普通的ListView。@Overridepublic boolean isItemViewTypePinned(int viewType) {boolean type = false;switch (viewType) {case Item.GROUP:type = true;break;case Item.CHILD:type = false;break;default:type = false;break;}return type;}}// 读取设备联系人的一般方法。大致流程就是这样,模板化的操作代码。private void readContacts(ArrayList<Contact> contacts) {Uri uri = Uri.parse("content://com.android.contacts/contacts");ContentResolver reslover = this.getContentResolver();// 在这里我们给query传递进去一个SORT_KEY_PRIMARY。// 告诉ContentResolver获得的结果按照联系人名字的首字母有序排列。Cursor cursor = reslover.query(uri, null, null, null,android.provider.ContactsContract.Contacts.SORT_KEY_PRIMARY);while (cursor.moveToNext()) {// 联系人IDString id = cursor.getString(cursor.getColumnIndex(android.provider.ContactsContract.Contacts._ID));// Sort Key,读取的联系人按照姓名从 A->Z 排序分组。String sort_key_primary = cursor.getString(cursor.getColumnIndex(android.provider.ContactsContract.Contacts.SORT_KEY_PRIMARY));// 获得联系人姓名String name = cursor.getString(cursor.getColumnIndex(android.provider.ContactsContract.Contacts.DISPLAY_NAME));Contact mContact = new Contact();mContact.id = id;mContact.name = name;mContact.sort_key_primary = sort_key_primary;// 获得联系人手机号码Cursor phone = reslover.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null,ContactsContract.CommonDataKinds.Phone.CONTACT_ID + "="+ id, null, null);// 取得电话号码(可能存在多个号码)// 因为同一个名字下,用户可能存有一个以上的号,// 遍历。ArrayList<String> phoneNumbers = new ArrayList<String>();while (phone.moveToNext()) {int phoneFieldColumnIndex = phone.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER);String phoneNumber = phone.getString(phoneFieldColumnIndex);phoneNumbers.add(phoneNumber);}mContact.phoneNumbers = phoneNumbers;contacts.add(mContact);}}// 自定义的一个用于装载从联系人数据库中读取到的数据。// 结构化数据,便于数据操作和访问。private class Contact {public String id;public String name;public String sort_key_primary;public ArrayList<String> phoneNumbers;// 获得一个联系人名字的首字符。// 比如一个人的名字叫“安卓”,那么这个人联系人的首字符是:A。public String firstLetterOfName() {String s = sort_key_primary.charAt(0) + "";return s.toUpperCase();}public String getPhoneNumbers() {String phones = " ";for (int i = 0; i < phoneNumbers.size(); i++) {phones += "号码" + i + ":" + phoneNumbers.get(i);}return phones;}}}




PinnedSectionListView.java

/* * Copyright (C) 2013 Sergej Shafarenka, halfbit.de * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file kt in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package com.hb.views;import zhangphil.contacts.BuildConfig;import android.content.Context;import android.database.DataSetObserver;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.PointF;import android.graphics.Rect;import android.graphics.drawable.GradientDrawable;import android.graphics.drawable.GradientDrawable.Orientation;import android.os.Parcelable;import android.util.AttributeSet;import android.view.MotionEvent;import android.view.SoundEffectConstants;import android.view.View;import android.view.ViewConfiguration;import android.view.accessibility.AccessibilityEvent;import android.widget.AbsListView;import android.widget.HeaderViewListAdapter;import android.widget.ListAdapter;import android.widget.ListView;import android.widget.SectionIndexer;/** * ListView, which is capable to pin section views at its top while the rest is * still scrolled. */public class PinnedSectionListView extends ListView {// -- inner classes/** * List adapter to be implemented for being used with PinnedSectionListView * adapter. */public static interface PinnedSectionListAdapter extends ListAdapter {/** * This method shall return 'true' if views of given type has to be * pinned. */boolean isItemViewTypePinned(int viewType);}/** Wrapper class for pinned section view and its position in the list. */static class PinnedSection {public View view;public int position;public long id;}// -- class fields// fields used for handling touch eventsprivate final Rect mTouchRect = new Rect();private final PointF mTouchPoint = new PointF();private int mTouchSlop;private View mTouchTarget;private MotionEvent mDownEvent;// fields used for drawing shadow under a pinned sectionprivate GradientDrawable mShadowDrawable;private int mSectionsDistanceY;private int mShadowHeight;/** Delegating listener, can be null. */OnScrollListener mDelegateOnScrollListener;/** Shadow for being recycled, can be null. */PinnedSection mRecycleSection;/** shadow instance with a pinned view, can be null. */PinnedSection mPinnedSection;/** * Pinned view Y-translation. We use it to stick pinned view to the next * section. */int mTranslateY;/** Scroll listener which does the magic */private final OnScrollListener mOnScrollListener = new OnScrollListener() {@Overridepublic void onScrollStateChanged(AbsListView view, int scrollState) {if (mDelegateOnScrollListener != null) { // delegatemDelegateOnScrollListener.onScrollStateChanged(view,scrollState);}}@Overridepublic void onScroll(AbsListView view, int firstVisibleItem,int visibleItemCount, int totalItemCount) {if (mDelegateOnScrollListener != null) { // delegatemDelegateOnScrollListener.onScroll(view, firstVisibleItem,visibleItemCount, totalItemCount);}// get expected adapter or fail fastListAdapter adapter = getAdapter();if (adapter == null || visibleItemCount == 0)return; // nothing to dofinal boolean isFirstVisibleItemSection = isItemViewTypePinned(adapter, adapter.getItemViewType(firstVisibleItem));if (isFirstVisibleItemSection) {View sectionView = getChildAt(0);if (sectionView.getTop() == getPaddingTop()) { // view sticks to// the top, no// need for// pinned shadowdestroyPinnedShadow();} else { // section doesn't stick to the top, make sure we have// a pinned shadowensureShadowForPosition(firstVisibleItem, firstVisibleItem,visibleItemCount);}} else { // section is not at the first visible positionint sectionPosition = findCurrentSectionPosition(firstVisibleItem);if (sectionPosition > -1) { // we have section positionensureShadowForPosition(sectionPosition, firstVisibleItem,visibleItemCount);} else { // there is no section for the first visible item,// destroy shadowdestroyPinnedShadow();}}};};/** Default change observer. */private final DataSetObserver mDataSetObserver = new DataSetObserver() {@Overridepublic void onChanged() {recreatePinnedShadow();};@Overridepublic void onInvalidated() {recreatePinnedShadow();}};// -- constructorspublic PinnedSectionListView(Context context, AttributeSet attrs) {super(context, attrs);initView();}public PinnedSectionListView(Context context, AttributeSet attrs,int defStyle) {super(context, attrs, defStyle);initView();}private void initView() {setOnScrollListener(mOnScrollListener);mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();initShadow(true);}// -- public API methodspublic void setShadowVisible(boolean visible) {initShadow(visible);if (mPinnedSection != null) {View v = mPinnedSection.view;invalidate(v.getLeft(), v.getTop(), v.getRight(), v.getBottom()+ mShadowHeight);}}// -- pinned section drawing methodspublic void initShadow(boolean visible) {if (visible) {if (mShadowDrawable == null) {mShadowDrawable = new GradientDrawable(Orientation.TOP_BOTTOM,new int[] { Color.parseColor("#ffa0a0a0"),Color.parseColor("#50a0a0a0"),Color.parseColor("#00a0a0a0") });mShadowHeight = (int) (8 * getResources().getDisplayMetrics().density);}} else {if (mShadowDrawable != null) {mShadowDrawable = null;mShadowHeight = 0;}}}/** Create shadow wrapper with a pinned view for a view at given position */void createPinnedShadow(int position) {// try to recycle shadowPinnedSection pinnedShadow = mRecycleSection;mRecycleSection = null;// create new shadow, if neededif (pinnedShadow == null)pinnedShadow = new PinnedSection();// request new view using recycled view, if suchView pinnedView = getAdapter().getView(position, pinnedShadow.view,PinnedSectionListView.this);// read layout parametersLayoutParams layoutParams = (LayoutParams) pinnedView.getLayoutParams();if (layoutParams == null) {layoutParams = (LayoutParams) generateDefaultLayoutParams();pinnedView.setLayoutParams(layoutParams);}int heightMode = MeasureSpec.getMode(layoutParams.height);int heightSize = MeasureSpec.getSize(layoutParams.height);if (heightMode == MeasureSpec.UNSPECIFIED)heightMode = MeasureSpec.EXACTLY;int maxHeight = getHeight() - getListPaddingTop()- getListPaddingBottom();if (heightSize > maxHeight)heightSize = maxHeight;// measure & layoutint ws = MeasureSpec.makeMeasureSpec(getWidth() - getListPaddingLeft()- getListPaddingRight(), MeasureSpec.EXACTLY);int hs = MeasureSpec.makeMeasureSpec(heightSize, heightMode);pinnedView.measure(ws, hs);pinnedView.layout(0, 0, pinnedView.getMeasuredWidth(),pinnedView.getMeasuredHeight());mTranslateY = 0;// initialize pinned shadowpinnedShadow.view = pinnedView;pinnedShadow.position = position;pinnedShadow.id = getAdapter().getItemId(position);// store pinned shadowmPinnedSection = pinnedShadow;}/** Destroy shadow wrapper for currently pinned view */void destroyPinnedShadow() {if (mPinnedSection != null) {// keep shadow for being recycled latermRecycleSection = mPinnedSection;mPinnedSection = null;}}/** Makes sure we have an actual pinned shadow for given position. */void ensureShadowForPosition(int sectionPosition, int firstVisibleItem,int visibleItemCount) {if (visibleItemCount < 2) { // no need for creating shadow at all, we// have a single visible itemdestroyPinnedShadow();return;}if (mPinnedSection != null&& mPinnedSection.position != sectionPosition) { // invalidate// shadow,// if// requireddestroyPinnedShadow();}if (mPinnedSection == null) { // create shadow, if emptycreatePinnedShadow(sectionPosition);}// align shadow according to next section position, if neededint nextPosition = sectionPosition + 1;if (nextPosition < getCount()) {int nextSectionPosition = findFirstVisibleSectionPosition(nextPosition, visibleItemCount- (nextPosition - firstVisibleItem));if (nextSectionPosition > -1) {View nextSectionView = getChildAt(nextSectionPosition- firstVisibleItem);final int bottom = mPinnedSection.view.getBottom()+ getPaddingTop();mSectionsDistanceY = nextSectionView.getTop() - bottom;if (mSectionsDistanceY < 0) {// next section overlaps pinned shadow, move it upmTranslateY = mSectionsDistanceY;} else {// next section does not overlap with pinned, stick to topmTranslateY = 0;}} else {// no other sections are visible, stick to topmTranslateY = 0;mSectionsDistanceY = Integer.MAX_VALUE;}}}int findFirstVisibleSectionPosition(int firstVisibleItem,int visibleItemCount) {ListAdapter adapter = getAdapter();int adapterDataCount = adapter.getCount();if (getLastVisiblePosition() >= adapterDataCount)return -1; // dataset has changed, no candidateif (firstVisibleItem + visibleItemCount >= adapterDataCount) {// added// to// prevent// index// Outofbound// (in// case)visibleItemCount = adapterDataCount - firstVisibleItem;}for (int childIndex = 0; childIndex < visibleItemCount; childIndex++) {int position = firstVisibleItem + childIndex;int viewType = adapter.getItemViewType(position);if (isItemViewTypePinned(adapter, viewType))return position;}return -1;}int findCurrentSectionPosition(int fromPosition) {ListAdapter adapter = getAdapter();if (fromPosition >= adapter.getCount())return -1; // dataset has changed, no candidateif (adapter instanceof SectionIndexer) {// try fast way by asking section indexerSectionIndexer indexer = (SectionIndexer) adapter;int sectionPosition = indexer.getSectionForPosition(fromPosition);int itemPosition = indexer.getPositionForSection(sectionPosition);int typeView = adapter.getItemViewType(itemPosition);if (isItemViewTypePinned(adapter, typeView)) {return itemPosition;} // else, no luck}// try slow way by looking through to the next section item abovefor (int position = fromPosition; position >= 0; position--) {int viewType = adapter.getItemViewType(position);if (isItemViewTypePinned(adapter, viewType))return position;}return -1; // no candidate found}void recreatePinnedShadow() {destroyPinnedShadow();ListAdapter adapter = getAdapter();if (adapter != null && adapter.getCount() > 0) {int firstVisiblePosition = getFirstVisiblePosition();int sectionPosition = findCurrentSectionPosition(firstVisiblePosition);if (sectionPosition == -1)return; // no views to pin, exitensureShadowForPosition(sectionPosition, firstVisiblePosition,getLastVisiblePosition() - firstVisiblePosition);}}@Overridepublic void setOnScrollListener(OnScrollListener listener) {if (listener == mOnScrollListener) {super.setOnScrollListener(listener);} else {mDelegateOnScrollListener = listener;}}@Overridepublic void onRestoreInstanceState(Parcelable state) {super.onRestoreInstanceState(state);post(new Runnable() {@Overridepublic void run() { // restore pinned view after configuration// changerecreatePinnedShadow();}});}@Overridepublic void setAdapter(ListAdapter adapter) {// assert adapter in debug modeif (BuildConfig.DEBUG && adapter != null) {if (!(adapter instanceof PinnedSectionListAdapter))throw new IllegalArgumentException("Does your adapter implement PinnedSectionListAdapter?");if (adapter.getViewTypeCount() < 2)throw new IllegalArgumentException("Does your adapter handle at least two types"+ " of views in getViewTypeCount() method: items and sections?");}// unregister observer at old adapter and register on new oneListAdapter oldAdapter = getAdapter();if (oldAdapter != null)oldAdapter.unregisterDataSetObserver(mDataSetObserver);if (adapter != null)adapter.registerDataSetObserver(mDataSetObserver);// destroy pinned shadow, if new adapter is not same as old oneif (oldAdapter != adapter)destroyPinnedShadow();super.setAdapter(adapter);}@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {super.onLayout(changed, l, t, r, b);if (mPinnedSection != null) {int parentWidth = r - l - getPaddingLeft() - getPaddingRight();int shadowWidth = mPinnedSection.view.getWidth();if (parentWidth != shadowWidth) {recreatePinnedShadow();}}}@Overrideprotected void dispatchDraw(Canvas canvas) {super.dispatchDraw(canvas);if (mPinnedSection != null) {// prepare variablesint pLeft = getListPaddingLeft();int pTop = getListPaddingTop();View view = mPinnedSection.view;// draw childcanvas.save();int clipHeight = view.getHeight()+ (mShadowDrawable == null ? 0 : Math.min(mShadowHeight,mSectionsDistanceY));canvas.clipRect(pLeft, pTop, pLeft + view.getWidth(), pTop+ clipHeight);canvas.translate(pLeft, pTop + mTranslateY);drawChild(canvas, mPinnedSection.view, getDrawingTime());if (mShadowDrawable != null && mSectionsDistanceY > 0) {mShadowDrawable.setBounds(mPinnedSection.view.getLeft(),mPinnedSection.view.getBottom(),mPinnedSection.view.getRight(),mPinnedSection.view.getBottom() + mShadowHeight);mShadowDrawable.draw(canvas);}canvas.restore();}}// -- touch handling methods@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {final float x = ev.getX();final float y = ev.getY();final int action = ev.getAction();if (action == MotionEvent.ACTION_DOWN && mTouchTarget == null&& mPinnedSection != null&& isPinnedViewTouched(mPinnedSection.view, x, y)) { // create// touch// target// user touched pinned viewmTouchTarget = mPinnedSection.view;mTouchPoint.x = x;mTouchPoint.y = y;// copy down event for eventually be used latermDownEvent = MotionEvent.obtain(ev);}if (mTouchTarget != null) {if (isPinnedViewTouched(mTouchTarget, x, y)) { // forward event to// pinned viewmTouchTarget.dispatchTouchEvent(ev);}if (action == MotionEvent.ACTION_UP) { // perform onClick on pinned// viewsuper.dispatchTouchEvent(ev);performPinnedItemClick();clearTouchTarget();} else if (action == MotionEvent.ACTION_CANCEL) { // cancelclearTouchTarget();} else if (action == MotionEvent.ACTION_MOVE) {if (Math.abs(y - mTouchPoint.y) > mTouchSlop) {// cancel sequence on touch targetMotionEvent event = MotionEvent.obtain(ev);event.setAction(MotionEvent.ACTION_CANCEL);mTouchTarget.dispatchTouchEvent(event);event.recycle();// provide correct sequence to super class for further// handlingsuper.dispatchTouchEvent(mDownEvent);super.dispatchTouchEvent(ev);clearTouchTarget();}}return true;}// call super if this was not our pinned viewreturn super.dispatchTouchEvent(ev);}private boolean isPinnedViewTouched(View view, float x, float y) {view.getHitRect(mTouchRect);// by taping top or bottom padding, the list performs on click on a// border item.// we don't add top padding here to keep behavior consistent.mTouchRect.top += mTranslateY;mTouchRect.bottom += mTranslateY + getPaddingTop();mTouchRect.left += getPaddingLeft();mTouchRect.right -= getPaddingRight();return mTouchRect.contains((int) x, (int) y);}private void clearTouchTarget() {mTouchTarget = null;if (mDownEvent != null) {mDownEvent.recycle();mDownEvent = null;}}private boolean performPinnedItemClick() {if (mPinnedSection == null)return false;OnItemClickListener listener = getOnItemClickListener();if (listener != null && getAdapter().isEnabled(mPinnedSection.position)) {View view = mPinnedSection.view;playSoundEffect(SoundEffectConstants.CLICK);if (view != null) {view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);}listener.onItemClick(this, view, mPinnedSection.position,mPinnedSection.id);return true;}return false;}public static boolean isItemViewTypePinned(ListAdapter adapter, int viewType) {if (adapter instanceof HeaderViewListAdapter) {adapter = ((HeaderViewListAdapter) adapter).getWrappedAdapter();}return ((PinnedSectionListAdapter) adapter).isItemViewTypePinned(viewType);}}

需要在AndroidManifest.xml添加读写通讯录权限和拨打电话权限:

 <!-- 拨打电话权限 -->    <uses-permission android:name="android.permission.CALL_PHONE" />     <!-- 写通讯录权限 -->    <uses-permission android:name="android.permission.WRITE_CONTACTS" />    <!-- 读通讯录权限 -->    <uses-permission android:name="android.permission.READ_CONTACTS" />


更多相关文章

  1. 【Android】Android(安卓)联系人数据库浅析之通话记录
  2. Android(安卓)获取Sim卡联系人
  3. 获取android联系人信息
  4. android 监听联系人数据库
  5. Android通讯录数据库介绍与基本操作(增删改查)
  6. Android通讯录数据库介绍与基本操作(增删改查)
  7. android(2.0以后版本) 中读取联系人和通话记录
  8. android获取手机通讯录联系人
  9. android-仿QQtab

随机推荐

  1. android 导航总结
  2. Android(安卓)Ticks: display text verti
  3. android照相及照片上传
  4. android 多点触摸实例
  5. java.net.SocketTimeoutException: Conne
  6. Android摄像头预览界面上画线
  7. Android(安卓)代码设置RelativeLayout元
  8. ANDROID NDK文档系列--(三)NDK Development
  9. android-制作透明按钮
  10. android wearable-Creating a 2D Picker,