Android应用Preference相关及源码浅析(Preference组件家族篇)

CheckPreference类似常见控件的CheckBox,一个item,右侧有一个CheckBox,用于通过SharePreferences存储操作的设置值,具体翻墙点我

如下是CheckPreference的相关属性介绍:

| attr | description |

| — | — |

| android:disableDependentsState | 与android:dependency相反;B可用,则A不可用;B不可用,则A可用。 |

| android:summaryOff | 选项未选中时显示的摘要。 |

| android:summaryOn | 选项被选中时显示的摘要。 |

2-2-3 独立控件EditTextPreference

EditTextPreference类似常见控件的EditText,一个item,点击弹出一个EditText的对话框,用于通过SharePreferences存储操作的设置值,具体翻墙点我

该控件无自有属性。具体不再介绍,相关方法查看官方API。

2-2-4 独立控件ListPreference

ListPreference类似常见控件的ListView,一个item,点击弹出一个ListView的Dialog,用于通过SharePreferences存储操作的设置值,具体翻墙点我

如下是ListPreference的相关属性介绍:

| attr | description |

| — | — |

| android:entries | list要显示的item数组名字。 |

| android:entryValues | list要显示的item数组值。 |

2-2-5 独立控件MultiSelectListPreference

MultiSelectListPreference类似常见控件的ListView,一个item,点击弹出一个多选的ListView的Dialog,用于通过SharePreferences存储操作的设置值,具体翻墙点我

MultiSelectListPreference的相关属性同上ListPreference。

2-2-6 独立控件SwitchPreference

SwitchPreference类似常见控件的Switch,一个item,右侧有一个Switch控件,用于通过SharePreferences存储操作的设置值,具体翻墙点我

如下是SwitchPreference的相关属性介绍:

| attr | description |

| — | — |

| android:disableDependentsState | 与android:dependency相反;B可用,则A不可用;B不可用,则A可用。 |

| android:summaryOff | 选项未选中时显示的摘要。 |

| android:summaryOn | 选项被选中时显示的摘要。 |

| android:switchTextOff | 关闭状态的文字提示。 |

| android:switchTextOn | 打开状态的文字提示。 |

2-2-7 独立控件RingtonePreference

RingtonePreference就是一个铃声选择item,点击弹出铃声选择list的dialog,用于通过SharePreferences存储操作的设置值,具体翻墙点我

如下是RingtonePreference的相关属性介绍:

| attr | description |

| — | — |

| android:ringtoneType | 铃声类型。ringtone/notification/alarm/all |

| android:showDefault | 选项中默认的铃声。 |

| android:showSilent | 是否显示静音项。 |

2-2-8 组合控件PreferenceScreen

PreferenceScreen就Preference hierarchy的root节点,实例化他可以使用createPreferenceScreen(Context)方法;这个类可以依附于两个地方,当一个preferenceactivity指向他时用来作为根布局显示偏好,当他嵌套出现在另一个Preference hierarchy内部时他会启动一个新的界面来显示子项Preference或者设置的intent;综上也就是说它不仅可以作为设置界面显示,而且还能够启动activity,具体翻墙点我

如下展示了作为根布局及子布局的两种情况:

<–! 作为根及子项展示设置界面>

<PreferenceScreen

xmlns:android=“http://schemas.android.com/apk/res/android”

android:key=“first_preferencescreen”>

<CheckBoxPreference

android:key=“wifi enabled”

android:title=“WiFi” />

<PreferenceScreen

android:key=“second_preferencescreen”

android:title=“WiFi settings”>

<CheckBoxPreference

android:key=“prefer wifi”

android:title=“Prefer WiFi” />

… other preferences here …

<–! 内嵌intent的模式>

<intent

android:targetPackage=“com.test.main”

android:targetClass=“com.test.main.activity”

2-2-9 组合控件PreferenceCategory

PreferenceCategory类似于LinearLayout,用于组合一组可设置标题的Preference,使布局更具备层次感,具体翻墙点我

这个类也没有啥特殊的东西介绍,详细参考API。

到此常用的Preference组件xml属性介绍完毕,对应的java方法就不再说明了,还有就是他们的protect方法也不再详细介绍,具体参见API。

2-3 新增Headers相关基础组件属性说明

上面我们简单介绍了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相关的组件及方法吧。

2-3-1 PreferenceActivity.Header相关属性方法使用基础

点我翻墙查看。Header继承自Object,实现了Parcelable,用来展示一个item的header。

相关属性如下:

| attr | description |

| — | — |

| android:icon | |

| android:breadCrumbShortTitle | 在fragment显示的短标题文字。 |

| android:breadCrumbTitle | 在fragment显示的标题文字。 |

| android:fragment | 当选择该头文件时,将显示该fragment的全名称。 |

| android:id | 唯一识别id。 |

| android:summary | item描述信息。 |

| android:title | item头名称。 |

如下是一个简单展示:

<?xml version="1.0" encoding="utf-8"?>

android:summary=“test 1”

android:title=“Name1”>

android:summary=“test”

android:title=“Name” >

<extra

android:name=“type”

android:value=“first” />

关于Header的用法下面会详细演示,基本情况就介绍到这里。

2-4 Preference相关组件显示操作控制API解释

有了上面Preference组件基本概念及属性介绍以后就相当于我们有了砖瓦,接下来就是咋盖房子了,也就是如何组合这些组件显示在屏幕上,我们现在就来看看这些常用的操作。

2-4-1 PreferenceActivity相关属性方法使用基础

翻墙点我查看。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相关的常用方法:

| method | description |

| — | — |

| public void addPreferencesFromIntent(Intent intent) | @deprecated,添加一个匹配intent的preferences activity。 |

| public void addPreferencesFromResource(int preferencesResId) | @deprecated,添加一个xml到activity。 |

| public Preference findPreference(CharSequence key) | @deprecated,查找一个指定key的Preference。 |

| public void finishPreferencePanel(Fragment caller, int resultCode, Intent resultData) | 结束指定的fragment,参数返回类似activity。 |

| public PreferenceManager getPreferenceManager() | @deprecated,获取activity使用的PreferenceManager实例。 |

| public PreferenceScreen getPreferenceScreen() | @deprecated,获取当前activity的根布局视图。 |

| public boolean hasHeaders() | 返回当前activity是否显示了header list。 |

| public void invalidateHeaders() | 刷新已经显示的header list,会重新回调onBuildHeaders()。 |

| public boolean isMultiPane() | 是否同时显示headers和fragment。 |

| public void loadHeadersFromResource(int resid, List target) | 解析一个headers的xml然后添加到target列表里。 |

| public void onBuildHeaders(List target) | 一般需要重写,注意!这个函数可能不是总会被调用,例如,如果该Activity已被要求显示一个特定的Fragment而不需要头文件,就不需要构建Headers,所以不调运。 |

| public Intent onBuildStartFragmentIntent(String fragmentName, Bundle args, int titleRes, int shortTitleRes) | 构造一个显示Fragment的Intent对象。 |

| public void onContentChanged() | 当界面发生变化时回调。 |

| public void onHeaderClick(PreferenceActivity.Header header, int position) | 当选择Headers列表项时调用,默认实现调用startwithfragment或switchtoheader。 |

| public boolean onIsMultiPane() | 大屏下默认实现是true。 |

| public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) | 当单击某个具有与它相关联的gragment类名称时调用。 |

| public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) | @deprecated,当Preference控件被点击时,触发该方法。参数preference为点击的对象,返回值true代表点击事件已成功捕捉,无须执行默认动作或者返回上层调用,例如,不跳转至默认Intent。 |

| public void setListFooter(View view) | 给Headers list设置foot view。 |

| public void startPreferenceFragment(Fragment fragment, boolean push) | 起一个fragment,push决定是否入栈。 |

| public void startPreferencePanel(String fragmentClass, Bundle args, int titleRes, CharSequence titleText, Fragment resultTo, int resultRequestCode) | 依据是否multi-pane模式启动一个preference的fragment(如果是小屏会重启一个activity显示)。 |

| public void startWithFragment(……) | 启动一个新的fragment。 |

| public void switchToHeader(……) | 在大屏multi-pane模式下切换到fragment显示给定参数的fragment。 |

2-4-2 PreferenceFragment相关属性方法使用基础

翻墙点我查看。PreferenceFragment继承自Fragment,这个类是3.0以后推荐使用的,用来处理碎片化问题。

该类的常用方法和上面PreferenceActivity的介绍差不多,这里不再详细说明,只是PreferenceActivity的@deprecated方法在PreferenceFragment中不是@deprecated的而已。

2-4-3 PreferenceManager相关方法使用基础

翻墙点我查看。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相关方法,如下:

| method | description |

| — | — |

| PreferenceManager.OnActivityDestroyListener | 当所依赖的activity销毁时回调接口。 |

| PreferenceManager.OnActivityResultListener | 当所依赖的activity得到返回result时回调接口。 |

| PreferenceManager.OnActivityStopListener | 当所依赖的activity停止时回调接口。 |

| public Preference findPreference(CharSequence key) | 通过key找到Preference。 |

| public static SharedPreferences getDefaultSharedPreferences(Context context) | 每个应用有一个默认的preferences文件,通过该方法获取。 |

| public SharedPreferences getSharedPreferences() | 通过PreferenceManager维护一个SharedPreference,可以调用PreferenceManager的API来设置name和mode。 |

| public int getSharedPreferencesMode() | 获取当前的mode。 |

| public String getSharedPreferencesName() | 获取当前的name。 |

| public static void setDefaultValues(Context context, String sharedPreferencesName, int sharedPreferencesMode, int resId, boolean readAgain) | 更加灵活的设置默认值,注意readAgain参数。 |

| public static void setDefaultValues(Context context, int resId, boolean readAgain) | 设置默认值,注意readAgain参数。 |

| public void setSharedPreferencesMode(int sharedPreferencesMode) | 设置当前的mode。 |

| public void setSharedPreferencesName(String sharedPreferencesName) | 设置当前的name。 |

可以看见,这个类其实也没啥介绍的,重点关注下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);

}

好了,控件使用就到这里了。

2-5 Preference控件家族实例

关于Preference控件家族的使用比较简单,自定义网上也一大把,所以不再给出例子。如果你想看例子可以参考如下:

其他的相关用法参考API及网络例子。

【工匠若水 http://blog.csdn.net/yanbober 转载请注明出处。点我开始Android技术交流

3 Preference组件源码设计简单分析


扯蛋了这么多,唉,叹个气继续吧,接下来就到了有意思的环节,源码结构简介。这里只是针对Preference控件特性介绍分析,不会过多追究View及Activity和Fragment细节,具体View及Activity和Fragment细节后面会写文章分析的。

3-1 PreferenceFragment源码浅析

首先还记得上面基础说了,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管理而已。

3-2 PreferenceActivity源码浅析

说到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源码如下:

<LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”

android:layout_width=“match_parent”

android:layout_height=“wrap_content”

android:minHeight=“48dp”

android:background=“?android:attr/activatedBackgroundIndicator”

android:gravity=“center_vertical”

android:paddingRight=“?android:attr/scrollbarSize”>

<ImageView

android:id=“@+id/icon”

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:layout_marginLeft=“6dip”

android:layout_marginRight=“6dip”

android:layout_gravity=“center” />

<RelativeLayout

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:layout_marginLeft=“2dip”

android:layout_marginRight=“6dip”

android:layout_marginTop=“6dip”

android:layout_marginBottom=“6dip”

android:layout_weight=“1”>

<TextView android:id=“@+android:id/title”

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:singleLine=“true”

android:textAppearance=“?android:attr/textAppearanceMedium”

android:ellipsize=“marquee”

android:fadingEdge=“horizontal” />

<TextView android:id=“@+android:id/summary”

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:layout_below=“@android:id/title”

android:layout_alignLeft=“@android:id/title”

android:textAppearance=“?android:attr/textAppearanceSmall”

android:ellipsize=“end”

android:maxLines=“2” />

**自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。**

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(资料价值较高,非无偿)

总结

本文讲解了我对Android开发现状的一些看法,也许有些人会觉得我的观点不对,但我认为没有绝对的对与错,一切交给时间去证明吧!愿与各位坚守的同胞们互相学习,共同进步!

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!

android:layout_height=“wrap_content”

android:layout_below=“@android:id/title”

android:layout_alignLeft=“@android:id/title”

android:textAppearance=“?android:attr/textAppearanceSmall”

android:ellipsize=“end”

android:maxLines=“2” />

**自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。**

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-s9z1N0HY-1711598437970)]

[外链图片转存中…(img-E84LAKV1-1711598437971)]

[外链图片转存中…(img-keFKh0of-1711598437971)]

[外链图片转存中…(img-Jphfv5uo-1711598437972)]

[外链图片转存中…(img-crTKDzRg-1711598437972)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(资料价值较高,非无偿)

总结

本文讲解了我对Android开发现状的一些看法,也许有些人会觉得我的观点不对,但我认为没有绝对的对与错,一切交给时间去证明吧!愿与各位坚守的同胞们互相学习,共同进步!

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值