編輯:關於Android編程
前一篇(點我閱讀前一篇《Android應用Preference相關及源碼淺析(SharePreferences篇)》)我們討論分析使用了Android的SharePreferences,相信看過的朋友都有了自己的感悟與理解,這一篇我們繼續乘熱打鐵來說說SharePreferences的衍生品—-Preference組件。
其實Preference組件大家一定不陌生,因為Android系統的Setting應用及我們市面上一些符合Android設計思想的應用的設置界面一般都會用它來實現,而且Google原生Android代碼中大量的使用了Preference組件。
簡單說,Preference組件其實就是Android常見UI組件與SharePreferences的組合封裝實現。
【工匠若水 http://blog.csdn.net/yanbober 轉載請注明出處。點我開始Android技術交流】
既然要先說說Preference組件家族基礎,那不得不先簡單說說這些Preference組件間的關系,如下一張圖是基於API 22繪制的一副Preference組件繼承關系圖:
怎麼樣?相比以前低版本的API來說,谷歌官方又增加了一些實用的Preference組件,不過遺憾的是這裡面有些是被hide掉的,有些是在com.android.internal.preference包下的,所以這些hide和com.android.internal.preference包的preference我們應用層是不能直接使用的。<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPrW9tMvE49a4tqi74cu1o6zV4tCp1+m8/rrNztLDx8a9yrHTw7XERWRpdFRleHS1yNfpvP663MDgy8ajrLb4ztLDx8a9yrHTw7XERWRpdFRleHS1yNfpvP7P1Mq+ysfSwMC109pBY3Rpdml0ebrNRnJhZ21lbnS1xKOsxMfDtNXiwO+1xFByZWZlcmVuY2XX6bz+ysfU9cO0z9TKvrXExNijvzwvcD4NCjxwPrTwsLi+zcrHUHJlZmVyZW5jZdfpvP7SstPQ19S8utLAwLXP1Mq+tcS/8rzco6zX7rOjvPu1xL7Nz/FQcmVmZXJlbmNlQWN0aXZpdHm1yKOsvt/M5b+0tPrC687Sw8e74beiz9bI58/Co7o8L3A+DQo8cHJlIGNsYXNzPQ=="brush:java;">
public abstract class PreferenceActivity extends ListActivity implements
PreferenceManager.OnPreferenceTreeClickListener,
PreferenceFragment.OnPreferenceStartFragmentCallback {
......
}
public abstract class PreferenceFragment extends Fragment implements
PreferenceManager.OnPreferenceTreeClickListener {
......
}
嘿嘿,明白了吧?其實Preference組件的使用及展示到Window的原理和普通EditText控件展示使用是類似的,因為Preference組件的顯示框架PreferenceActivity及PreferenceFragment都是從基本的Activity及Fragment繼承而來,只是針對Preference進行了二次封裝而已。
到此對Preference組件已經有一個基本的概念認識了,接下來我們就一步一步往下看。
我們可以發現,Preference組件家族的控件還是比較豐富的,這裡肯定不能一一介紹,所以還是代表性的說幾個使用頻率最高作為指引就行了,其他的用到時參考相關官方API或者源碼即可。
所有Preference組件的基類,類似常見控件的TextView,一個單純的item,用於通過SharePreferences存儲操作的設置值,具體翻牆點我。
如下是基類Preference的相關屬性介紹:
關於基類Preference提供的方法這裡就不再詳細列出了,如需查看,具體翻牆點我。
CheckPreference類似常見控件的CheckBox,一個item,右側有一個CheckBox,用於通過SharePreferences存儲操作的設置值,具體翻牆點我。
如下是CheckPreference的相關屬性介紹:
EditTextPreference類似常見控件的EditText,一個item,點擊彈出一個EditText的對話框,用於通過SharePreferences存儲操作的設置值,具體翻牆點我。
該控件無自有屬性。具體不再介紹,相關方法查看官方API。
ListPreference類似常見控件的ListView,一個item,點擊彈出一個ListView的Dialog,用於通過SharePreferences存儲操作的設置值,具體翻牆點我。
如下是ListPreference的相關屬性介紹:
MultiSelectListPreference類似常見控件的ListView,一個item,點擊彈出一個多選的ListView的Dialog,用於通過SharePreferences存儲操作的設置值,具體翻牆點我。
MultiSelectListPreference的相關屬性同上ListPreference。
SwitchPreference類似常見控件的Switch,一個item,右側有一個Switch控件,用於通過SharePreferences存儲操作的設置值,具體翻牆點我。
如下是SwitchPreference的相關屬性介紹:
RingtonePreference就是一個鈴聲選擇item,點擊彈出鈴聲選擇list的dialog,用於通過SharePreferences存儲操作的設置值,具體翻牆點我。
如下是RingtonePreference的相關屬性介紹:
ringtone/notification/alarm/all
android:showDefault
選項中默認的鈴聲。
android:showSilent
是否顯示靜音項。
PreferenceScreen就Preference hierarchy的root節點,實例化他可以使用createPreferenceScreen(Context)方法;這個類可以依附於兩個地方,當一個preferenceactivity指向他時用來作為根布局顯示偏好,當他嵌套出現在另一個Preference hierarchy內部時他會啟動一個新的界面來顯示子項Preference或者設置的intent;綜上也就是說它不僅可以作為設置界面顯示,而且還能夠啟動activity,具體翻牆點我。
如下展示了作為根布局及子布局的兩種情況:
<--! 作為根及子項展示設置界面>
... other preferences here ...
<--! 內嵌intent的模式>
PreferenceCategory類似於LinearLayout,用於組合一組可設置標題的Preference,使布局更具備層次感,具體翻牆點我。
這個類也沒有啥特殊的東西介紹,詳細參考API。
到此常用的Preference組件xml屬性介紹完畢,對應的java方法就不再說明了,還有就是他們的protect方法也不再詳細介紹,具體參見API。
上面我們簡單介紹了PreferenceScreen相關xml的屬性,這些其實是老版本的處理方式;自從Android 3.0引入Fragment之後,Preference相關的控件也有了變化。
由於PreferenceActivity在3.0開始也需要能夠處理多屏幕碎片化問題,所以Android 3.0之前采用PreferenceScreen嵌套的方法來跳轉分類細則,而Android 3.0及之後使用了Preference Headers的方法來適配多屏幕碎片化問題。
他的核心就是在主屏中通過headers的xml布局列出所有的主題設置項,每個主題設置的詳細設置由各自指定的PreferenceFragment負責,而各自的PreferenceFragment可以如傳統的PreferenceActivity 一樣布局自身的PreferenceScreen。
preference-headers就是他們的root,既然這樣,那我們就來看看Headers相關的組件及方法吧。
點我翻牆查看。Header繼承自Object,實現了Parcelable,用來展示一個item的header。
相關屬性如下:
如下是一個簡單展示:
......
......
關於Header的用法下面會詳細演示,基本情況就介紹到這裡。
有了上面Preference組件基本概念及屬性介紹以後就相當於我們有了磚瓦,接下來就是咋蓋房子了,也就是如何組合這些組件顯示在屏幕上,我們現在就來看看這些常用的操作。
翻牆點我查看。PreferenceActivity繼承自ListActivity,這個類是Preference相關控件展示的基類,在Android 3.0以前推薦直接使用,3.0以後推薦和preferencefragment一起使用,所以你可以看見PreferenceActivity中有些方法現在已經是過時的了。
首先看下PreferenceActivity加載xml目錄下的文件使用的方法,如下:
public class DemoActivity extends PreferenceActivity {
@Override
public void onBuildHeaders(List
target) { super.onBuildHeaders(target); //當大於等於3.0版本時推薦重寫該方法加載xml,headers+fragments模式 loadHeadersFromResource(R.xml.preference_header, target); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { //當小於3.0版本時推薦重寫該方法加載xml,當然大於時也可以用,只是不推薦而已 addPreferencesFromResource(R.xml.preference); } } }
如下我們來看看PreferenceActivity相關的常用方法:
翻牆點我查看。PreferenceFragment繼承自Fragment,這個類是3.0以後推薦使用的,用來處理碎片化問題。
該類的常用方法和上面PreferenceActivity的介紹差不多,這裡不再詳細說明,只是PreferenceActivity的@deprecated方法在PreferenceFragment中不是@deprecated的而已。
翻牆點我查看。PreferenceManager繼承自Object,這個類其實我們前一篇《Android應用Preference相關及源碼淺析(SharePreferences篇)》獲取Preference實例就該說明的,這裡才說而已。
Android中得到SharedPreference的方式有四種:
ContextWrapper.getSharedPreferences(String name, int mode)
可以自己設置SharedPreference的名字與模式。
Activity.getPreferences(int mode)
name是Activity名字,不能設置。
PreferenceManager.getSharedPreferences()
通過PreferenceManager維護一個SharedPreference,我們可以調用PreferenceManager的API來設置name和mode,並且最終也是調用到ContextWrapper的getSharedPreferences。
PreferenceManager.getDefaultSharedPreferences(Context context)
得到的SharedPreference是某個包名下共享私有的,不能讓其他的包訪問,而且name和mode不能設置,最終也會調用到ContextWrapper的getSharedPreferences。
接下來簡單看下PreferenceManager相關方法,如下:
可以看見,這個類其實也沒啥介紹的,重點關注下setDefaultValues的幾個核心參數就行。如果我們的設置項很多,而且每項在代碼中都需要設置默認缺省值,那就推薦使用setDefaultValues方法。在應用第一次運行時,從preference的xml中獲取缺省值,並生成文件保存(如果已經有一個SharedPrefferences對象,也會進行更新,就像下面代碼中三四行對調);不是第一運行就不會改現有保存值。
protected void onCreate(Bundle savedInstanceState) {
......
PreferenceManager.setDefaultValues(this, R.xml.default_value, false);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
String option = prefs.getString(key, null);
}
好了,控件使用就到這裡了。
關於Preference控件家族的使用比較簡單,自定義網上也一大把,所以不再給出例子。如果你想看例子可以參考如下:
官方Settings設計原理。
Settings源碼。
其他的相關用法參考API及網絡例子。
【工匠若水 http://blog.csdn.net/yanbober 轉載請注明出處。點我開始Android技術交流】
扯蛋了這麼多,唉,歎個氣繼續吧,接下來就到了有意思的環節,源碼結構簡介。這裡只是針對Preference控件特性介紹分析,不會過多追究View及Activity和Fragment細節,具體View及Activity和Fragment細節後面會寫文章分析的。
首先還記得上面基礎說了,PreferenceFragment使用第一步就是使用其內部方法addPreferencesFromResource或者addPreferencesFromIntent設置源。所以這裡我們以addPreferencesFromResource為例來說明,如下源碼:
//PreferenceFragment的方法
public void addPreferencesFromResource(int preferencesResId) {
//判斷異常說明了該方法至少得在super.onCreate方法之後調運,以便初始化PreferenceManager
requirePreferenceManager();
//這個前面也介紹過的,設置根布局PreferenceScreen
setPreferenceScreen(mPreferenceManager.inflateFromResource(getActivity(),
preferencesResId, getPreferenceScreen()));
}
接著我們看下setPreferenceScreen方法源碼,如下:
public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
//設置根布局到PreferenceManager裡
if (mPreferenceManager.setPreferences(preferenceScreen) && preferenceScreen != null) {
//空方法
onUnbindPreferences();
//設置標記,在onActivityCreated方法中有用
mHavePrefs = true;
//決定是否重設bind布局,核心都是為了執行bindPreferences方法
if (mInitDone) {
postBindPreferences();
}
}
}
到此接下來就是bind了,至於在這裡通過Handler發消息bindPreferences還是在onActivityCreated自動調bindPreferences方法取決於你把addPreferencesFromResource方法寫在那個生命周期方法裡。如下我們直接來看bindPreferences方法,如下源碼:
//這個方法是搭建顯示的核心方法!!!!!!!!!
private void bindPreferences() {
//拿到PreferenceManager中存的根視圖PreferenceScreen
final PreferenceScreen preferenceScreen = getPreferenceScreen();
if (preferenceScreen != null) {
//傳遞當前ListView到preferenceScreen的bind方法
preferenceScreen.bind(getListView());
}
//PreferenceFragment的空方法
onBindPreferences();
}
到此可以看見PreferenceFragment裡bind最終是交給了PreferenceScreen的bind來關聯PreferenceFragment的ListView與PreferenceScreen的ListAdapter。我們現在就來看下PreferenceScreen的bind源碼,如下:
//PreferenceScreen類的方法
public void bind(ListView listView) {
//設置listview的item監聽
listView.setOnItemClickListener(this);
//PreferenceScreen中bind的重點核心!!!!!!!!!!!!!給listview設置adapter
listView.setAdapter(getRootAdapter());
//一些register操作,忽略
onAttachedToActivity();
}
好了,我們還是來關注這個adapter咋來的吧,如下就是getRootAdapter方法源碼:
public ListAdapter getRootAdapter() {
if (mRootAdapter == null) {
mRootAdapter = onCreateRootAdapter();
}
return mRootAdapter;
}
protected ListAdapter onCreateRootAdapter() {
return new PreferenceGroupAdapter(this);
}
終於真相快要大白了,PreferenceFragment的listview設置的adapter原來是PreferenceGroupAdapter。哈哈,我們繼續來看看這個類,如下:
//hide類,專門用來Preference的list顯示的adapter
public class PreferenceGroupAdapter extends BaseAdapter
implements OnPreferenceChangeInternalListener {
//省略相關屬性定義
......
//構造方法,傳入的是PreferenceScreen根布局
public PreferenceGroupAdapter(PreferenceGroup preferenceGroup) {
......
//sync設置相關list列表數據後通知listview刷新
syncMyPreferences();
}
private void syncMyPreferences() {
......
//通知listview刷新當前准備的Preference列表
notifyDataSetChanged();
......
}
//省略一堆方法
......
//notifyDataSetChanged後和普通adapter一樣item繪制會回調getView方法
public View getView(int position, View convertView, ViewGroup parent) {
//拿到當前item的Preference組件
final Preference preference = this.getItem(position);
......
//調運Preference的getView方法得到當前item真正的view顯示,這是核心!!!!!!!!!!!!
//關於Preference的getView方法下面分析Preference源碼會說到的,或者你可以直接跳到Preference源碼分析部分查看。
View result = preference.getView(convertView, parent);
......
return result;
}
......
}
到此你會發現,其實無非就是ListView和Adapter的關系,而Adapter的getView所得到的View由Preference提供而已,而Adapter由PreferenceScreen管理而已。
說到PreferenceActivity現在不推薦的addPreferencesFromResource方法時其實是沒啥解釋的,這種模式現在被官方推薦通過PreferenceFragment的addPreferencesFromResource來實現,所以也就是說關於PreferenceActivity的addPreferencesFromResource方法(也就是在PreferenceActivity中直接添加Preference組件)其顯示原理和上面分析的PreferenceFragment是一樣的,所以這裡就不再過多解釋了。
我們把重點放在loadHeadersFromResource方法上,也就是現在推薦的PreferenceActivity放置Headers模式。接下來就來分析分析吧。
public abstract class PreferenceActivity extends ListActivity implements
PreferenceManager.OnPreferenceTreeClickListener,
PreferenceFragment.OnPreferenceStartFragmentCallback {
......
//省略一堆方法
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//設置基礎布局
setContentView(com.android.internal.R.layout.preference_list_content);
//獲取一些ContentView裡的控件實例
......
//判斷是啥模式,左右展示還是單頁
boolean hidingHeaders = onIsHidingHeaders();
mSinglePane = hidingHeaders || !onIsMultiPane();
//獲取fragment參數(其實是PreferenceActivity中點擊Header item重啟PreferenceActivity時傳遞的)
String initialFragment = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);
Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
int initialTitle = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_TITLE, 0);
int initialShortTitle = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_SHORT_TITLE, 0);
if (savedInstanceState != null) {
...... //忽略,非重點主線
} else {
if (initialFragment != null && mSinglePane) {
//SinglePane時有參數則替換顯示Fragment
switchToHeader(initialFragment, initialArguments);
......
} else {
//核心方法之一!!!!!!!!!!!!
//記得上面基礎使用介紹過嗎?新的實現重寫onBuildHeaders空方法,在其中
//調運loadHeadersFromResource方法加載header list xml文件
onBuildHeaders(mHeaders);
//如果存在header list則走這裡(上面onBuildHeaders裡會組織生成mHeaders的list結構)
if (mHeaders.size() > 0) {
//header-fragment左右各半屏模式
if (!mSinglePane) {
if (initialFragment == null) {
//設置顯示header
Header h = onGetInitialHeader();
switchToHeader(h);
} else {
//設置顯示header及fragment
switchToHeader(initialFragment, initialArguments);
}
}
}
}
}
if (initialFragment != null && mSinglePane) {
//當SinglePane加載的是Fragment時隱藏header,顯示fragment
findViewById(com.android.internal.R.id.headers).setVisibility(View.GONE);
mPrefsContainer.setVisibility(View.VISIBLE);
......
} else if (mHeaders.size() > 0) {
//重點!!!!!!!!!!!!!!!!!這就是要分析的header的listview的adapter放置地
setListAdapter(new HeaderAdapter(this, mHeaders));
......
} else {
//這就是最原始的供已經不推薦的addPreferencesFromResource方式加載Preference組件了
//具體原理同上PreferenceFragment的加載顯示原理了,不再分析
setContentView(com.android.internal.R.layout.preference_list_content_single);
......
}
//其他初始設置
......
}
}
通過上面的分析可以看見其實對於Header的adapter核心就是setListAdapter(new HeaderAdapter(this, mHeaders));這句代碼。那我們就來看看這個內部類HeaderAdapter,源碼如下:
//可以發現PreferenceActivity的內部類HeaderAdapter是繼承自ArrayAdapter的,
//這個Adapter就是用來給推薦的Header list的listview提供數據的。
private static class HeaderAdapter extends ArrayAdapter
{ //Holder裡只有最典型經典的三個組件 private static class HeaderViewHolder { ImageView icon; TextView title; TextView summary; } private LayoutInflater mInflater; //構造方法,不解釋 public HeaderAdapter(Context context, List
objects) { super(context, 0, objects); mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); } //最最核心方法!!!!!!Header list被顯示到PreferenceActivity的listview關鍵點 @Override public View getView(int position, View convertView, ViewGroup parent) { HeaderViewHolder holder; View view; //再常見不過的Adapter數據加載ViewHolder寫法了 if (convertView == null) { //加載header的item布局,都是用的preference_header_item文件,如下會介紹 view = mInflater.inflate(com.android.internal.R.layout.preference_header_item, parent, false); holder = new HeaderViewHolder(); holder.icon = (ImageView) view.findViewById(com.android.internal.R.id.icon); holder.title = (TextView) view.findViewById(com.android.internal.R.id.title); holder.summary = (TextView) view.findViewById(com.android.internal.R.id.summary); view.setTag(holder); } else { view = convertView; holder = (HeaderViewHolder) view.getTag(); } //一堆顯示,通過getItem(position)拿到構造裡傳入的List
類型objects的item // All view fields must be updated every time, because the view may be recycled Header header = getItem(position); holder.icon.setImageResource(header.iconRes); holder.title.setText(header.getTitle(getContext().getResources())); CharSequence summary = header.getSummary(getContext().getResources()); if (!TextUtils.isEmpty(summary)) { holder.summary.setVisibility(View.VISIBLE); holder.summary.setText(summary); } else { holder.summary.setVisibility(View.GONE); } return view; } }
可以看見這個adapter的getView中的item核心是加載了一個preference_header_item的xml文件,然後設置作為item的header。這個xml源碼如下:
哈哈,到此就不在解釋啥了,很直觀了,就是這麼任性,就是這麼簡單的實現了Header List的顯示。
說這個的原因是上面PreferenceFragemnt分析加載設置adapter的getView方法時留下的歷史問題。我們先來看看這個文件的核心代碼,後面總結串起來你就明白了,如下源碼:
//可以看見,他不是一個View,但是組合管理了一個View和PreferenceManager
public class Preference implements Comparable {
......
//各種屬性
......
private PreferenceManager mPreferenceManager;
//重點關注,和自定義及Preference顯示原理息息相關,preference就是下面列出的xml資源
private int mLayoutResId = com.android.internal.R.layout.preference;
private int mWidgetLayoutResId;
......
//各種getXXX及setXXX方法
......
/**
* Gets the View that will be shown in the {@link PreferenceActivity}.
* 獲取Preference的item顯示view
*/
public View getView(View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = onCreateView(parent);
}
onBindView(convertView);
return convertView;
}
protected View onCreateView(ViewGroup parent) {
final LayoutInflater layoutInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
final View layout = layoutInflater.inflate(mLayoutResId, parent, false);
final ViewGroup widgetFrame = (ViewGroup) layout.findViewById(com.android.internal.R.id.widget_frame);
if (widgetFrame != null) {
//mWidgetLayoutResId有專門的set方法可以設置或者重寫
if (mWidgetLayoutResId != 0) {
//android:id/widget_frame為mWidgetLayoutResId所對應的布局預留空間插入
layoutInflater.inflate(mWidgetLayoutResId, widgetFrame);
} else {
//默認實現是null的
widgetFrame.setVisibility(View.GONE);
}
}
return layout;
}
/**
* Binds the created View to the data for this Preference.
*
* This is a good place to grab references to custom Views in the layout and
* set properties on them.
*
*/ protected void onBindView(View view) { //設置子View相關屬性 final TextView titleView = (TextView) view.findViewById(com.android.internal.R.id.title); if (titleView != null) { final CharSequence title = getTitle(); if (!TextUtils.isEmpty(title)) { titleView.setText(title); titleView.setVisibility(View.VISIBLE); } else { titleView.setVisibility(View.GONE); } } ...... //類似的各種子View設置操作,不再列出 } ...... }
可以看見,這個getView其實就是上面PreferenceFragment分析中Adapter中getView調運的Preference的getView。怎麼樣,串起來吧。也就是說Preference不是View,但是他提供View給ListView的每一個Item顯示,其提供的View的基類布局(上面Preference類中mLayoutResId屬性的值)如下:
哈哈,不用解釋了吧,這下相信你可以將前面基礎使用和源碼分析幾部分完全串起來理解了吧。
通過上面分析可以知道Preference其實不是View,但是其內部創建管理了一個View(ListView的item,被Adapter的getView通過Preference.getView方法獲得顯示)。
可以看出來,上面我們分析Preference的onCreateView、getView、onBindView這幾個方法其實是整個Preference組件顯示等的核心方法,所以正如系統提供的Preference的各種實用子類一樣,當我們想自定義Preference的時候完全可以重寫這些方法來得到自己的各種自定義View,這樣就完美的解決了代碼的擴展性,我們不用去修改ListAdapter的實現就能實現自定義的Preference,所以說可見Google的工程師在設計Preference結構時是多麼的牛叉,不得不膜拜。
【工匠若水 http://blog.csdn.net/yanbober 轉載請注明出處。點我開始Android技術交流】
題外話:其實這篇文章是受我一個朋友邀請幫忙寫的。還記得去年年初我在上家公司(做Android盒子)負責修改一個項目的Settings源碼,添加一個屏幕縮放功能在Settings裡面。後來做好以後維護轉手給了別人,當時別人是個新手,各種問。所以落下後遺症,於是乎就在邀請之下打算寫了這一系列兩篇文章,以幫助快速上手原生Settings的修改。
其實沒啥總結的,還記得前段時間在網上看見有人吐槽Preference是google設計的一個失敗品,一點也不好用啥的。其實我想說Preference的設計還是不錯的,是值得借鑒的,隨便舉個例子如下:
在布局設計上可以保持統一預留差異區域供自定義動態插入,達到復用的目的。
在實現listView各個item不同的Adapter的getView方法時不用像傳統那樣if-else或者switch操作,而是預留一個基類用於實現回調,這樣更加靈活。
其他的慢慢體會就行了。
所以說Preference的設計還是非常牛逼靈活的,對那些在網上扯蛋不好用的人說No!!!
最近在看一本古董書《50 Android Hacks》,而書中開篇的第一個Hack就是”使用weight屬性實現視圖的居中現實“。事實上weigh
Android手機字母A-Z排序側邊索引是非常常見的功能,在此提供快速集成框架.教你用Android studio工具一分鐘搞定這個效果.實現效果:以及點擊F跳轉效果&n
據說Android最推薦的是在ViewPager中使用FragMent,即ViewPager中的頁面不像前面那樣用LayoutInflater直接從布局文件加載,而是一個
Android內核中判別啟動模式 問題出現在驅動組描述了一個問題是內核的一個驅動中設置的電壓值在正常模式和其它模式啟動時電壓值