Android开发框架Collection,Android开发经验谈

| setAutoRefresh | 自动刷新 |
| refreshComplete | 刷新数据完成 |
| loadMoreComplete | 加载更多数据完成 |
| setPullRefreshEnabled | 是否允许刷新 |
| setLoadMoreEnabled | 是否允许加载更多 |
| setRefreshTimeVisible | 显示加载更新时间 |
| isLoading | 是否正在loading数据 |
| isRefreshing | 正在refreshing数据 |
| setRefreshAndLoadMoreListener | 刷新和加载更多回调 |
| destroy | 内存回收 |

1.框架默认下拉刷新、上拉加载更多样式

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(1)布局文件

<com.youngmanster.collection_kotlin.recyclerview.PullToRefreshRecyclerView
android:id=“@+id/recycler_rv”
android:layout_width=“match_parent”
android:layout_height=“match_parent” />

(2)代码设置

recycler_rv.setPullRefreshEnabled(true);
recycler_rv.setLoadMoreEnabled(true);

2、自定义下拉刷新、上拉加载更多样式

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

刷新几种状态:
属性作用
STATE_PULL_DOWN拉的状态(还没到下拉到固定的高度时)
STATE_RELEASE_REFRESH下拉到固定高度提示释放刷新的状态
STATE_REFRESHING正在刷新状态
STATE_DONE刷新完成
加载更多几种状态:
属性作用
STATE_LOADING正在加载
STATE_COMPLETE加 载完成
STATE_NODATA没有数据
(1)代码设置

recycler_rv.setPullRefreshEnabled(true);
recycler_rv.setLoadMoreEnabled(true);
recycler_rv.setRefreshAndLoadMoreListener(this);
recycler_rv.setRefreshView(new DefinitionAnimationRefreshHeaderView(getActivity()));
recycler_rv.setLoadMoreView(new DefinitionAnimationLoadMoreView(getActivity()));

自定义刷新的步骤:
①自定义View继承BasePullToRefreshView,重写initView()、setRefreshTimeVisible(boolean show)、destroy()方法:

1.在initView()做自定义布局、相关动画的初始化,最后在initView()方法的最后面添加以下代码即可。

mContainer = LayoutInflater.from(context).inflate(R.layout.collection_library_layout_default_arrow_refresh, null);
//把刷新头部的高度初始化为0
LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
lp.setMargins(0, 0, 0, 0);
this.setLayoutParams(lp);
this.setPadding(0, 0, 0, 0);
addView(mContainer, new LayoutParams(LayoutParams.MATCH_PARENT, 0));
setGravity(Gravity.BOTTOM);
//测量高度
measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
mMeasuredHeight = getMeasuredHeight();

2.setRefreshTimeVisible(boolean show)是用来设置是否显示刷新时间控件,在默认刷新样式中通过mRecyclerView.setRefreshTimeVisible(false)即可隐藏刷新时间,如果在自定义的布局中没有这项这个方法就可以忽略。

3.destroy()是用来关掉改页面时把刷新View的一些动画等释放,防止内存泄漏。

②实现BasePullToRefreshView.OnStateChangeListener监听(重点,主要是进行状态切换后的相关操作逻辑)

1.在构造函数中设置 onStateChangeListener=this 2.onStateChange的模板样式

@Override
public void onStateChange(int state) {
//下拉时状态相同不做继续保持原有的状态
if (state == mState) return ;
//根据状态进行动画显示
switch (state){
case STATE_PULL_DOWN:
clearAnim();
startAnim();
break;
case STATE_RELEASE_REFRESH:
break;
case STATE_REFRESHING:
clearAnim();
startAnim();
scrollTo(mMeasuredHeight);
break;
case STATE_DONE:
break;
}
mState = state;
}

自定义加载更多的步骤(包括没有更多数据显示的操作):
①自定义View继承BaseLoadMoreView,重写initView()、setState()、destroy()方法:

1.在initView()做自定义布局、相关动画的初始化,最后在initView()方法的最后面添加以下代码即可

mContainer = LayoutInflater.from(context)
.inflate(R.layout.layout_definition_animation_loading_more, null)
addView(mContainer)
gravity = Gravity.CENTER

2.destroy()是用来关掉改页面时把刷新View的一些动画等释放,防止内存泄漏。 3.在setState()进行状态切换后的相关操作逻辑,模板样式

@Override
public void setState(int state) {

if(isDestroy){
return;
}
switch (state){
case STATE_LOADING:
loadMore_Ll.setVisibility(VISIBLE);
noDataTv.setVisibility(INVISIBLE);
animationDrawable= (AnimationDrawable) loadingIv.getDrawable();
animationDrawable.start();
this.setVisibility(VISIBLE);
break;
case STATE_COMPLETE:
if(animationDrawable!=null){
animationDrawable.stop();
}
this.setVisibility(GONE);
break;
case STATE_NODATA:
loadMore_Ll.setVisibility(INVISIBLE);
noDataTv.setVisibility(VISIBLE);
animationDrawable= (AnimationDrawable) loadingIv.getDrawable();
animationDrawable.start();
this.setVisibility(VISIBLE);
break;
}

mState = state;

}

4.注意:在自定义加载更多样式时,如果需要有没有更多加载更多数据提示同样需要在布局中写好,然后在onSatae中根据状态对加载和没有跟多显示提示进行显示隐藏操作。

3、上拉加载更多配合SwipeRefreshLayout使用

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(1)布局文件

<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id=“@+id/swipeRefreshLayout”
android:layout_width=“match_parent”
android:layout_height=“match_parent”>

<com.youngmanster.collection_kotlin.recyclerview.PullToRefreshRecyclerView
android:id=“@+id/recycler_rv”
android:layout_width=“match_parent”
android:layout_height=“match_parent”/>

</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

(2)代码设置

recycler_rv.setLoadMoreEnabled(true);
recycler_rv.setRefreshAndLoadMoreListener(this);
recycler_rv.setLoadMoreView(DefinitionAnimationLoadMoreView(activity));
swipeRefreshLayout.setColorSchemeResources(R.color.colorAccent);
swipeRefreshLayout.setOnRefreshListener(this);

(3)注意的问题

由于PullToRefreshRecyclerView的下拉刷新和下拉加载更多完成时会自动刷新Adapter,而SwipeRefreshLayout刷新完成时需要手动进行notifyDataSetChanged刷新适配器。

4、RecyclerView添加头部、空布局

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

View emptyView = LayoutInflater.from(getActivity()).inflate(R.layout.layout_empty,null);
mRecyclerView.setEmptyView(emptyView);
emptyView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
for(int i=0;i<10;i++){
mDatas.add(“item”+i);
}
definitionRefreshAdapter.notifyDataSetChanged();
}
});

5、上拉加载更多实现NoMoreData、自动刷新

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(1)上拉加载更多数据的布局设置在上面的自定义LoadingMoreView中有介绍,如果要显示没有更多数据提示只需要在LoadMore返回数据之后设置:

mRecyclerView.setNoMoreDate(true)

(2)自动刷新需要列表已经填充了数据之后再做自动刷新操作才会生效:

mRecyclerView.setAutoRefresh()

6.Collection库的刷新加载文本已经适配了中文、英文、繁体,如果对默认刷新加载显示文本进行修改,通过LoadingTextConfig设置,可在Application全局设置

LoadingTextConfig textConfig=LoadingTextConfig.getInstance(getApplicationContext());
textConfig
.setCollectionLoadingMore(“加载更多”)
.setCollectionLastRefreshTimeTip(“更新时间:”)
.setCollectionNoMoreData(“没有更多数据”)
.setCollectionPullReleaseText(“释放刷新”)
.setCollectionRefreshing(“正在刷新”)
.setCollectionPullDownRefreshText(“下拉刷新”)
.setCollectionRefreshDone(“加载完成”);
PullToRefreshRecyclerViewUtils.loadingTextConfig=textConfig;

7、PullToRefreshRecyclerView的其他使用以及注意问题

1.下面是下拉刷新上拉加载更多的一些操作模板

public void refreshUI() {
if (defaultRefreshAdapter == null) {
defaultRefreshAdapter = new DefaultRecyclerAdapter(getActivity(), mDatas, mRecyclerView);
mRecyclerView.setAdapter(defaultRefreshAdapter);
} else {
if (mRecyclerView != null) {
if (mRecyclerView.isLoading()) {
mRecyclerView.loadMoreComplete();
} else if (mRecyclerView.isRefreshing()) {
mRecyclerView.refreshComplete();
}
}
}
}

2.注意问题

①在设置RecyclerView是要设LayoutManager
②如果使用PullToRefreshRecyclerView在Activty/Fragment中的onDestroy()调用mRecyclerView.destroy()防止内存泄漏。

@Override
public void onDestroy() {
super.onDestroy();
mRecyclerView.destroy()
}

③设置refreshRv.setLoadMoreEnabled(true),当填充的数据的列表size为0的同时还通过RecyclerView设置分割线底部就会出现一个空白的item,这个item就是加载更多显示的Item。
解决办法:不通过RecyclerView设置分割线,直接在布局自定义分割线。

三、BaseRecyclerViewAdapter的使用

1.BaseRecyclerViewAdapter的比原始Adapter的代码量减小

在BaseRecyclerViewAdapter中的BaseViewHolder进行布局转化,同时定义了一些比较基本的View操作,使用简单。

(1)使用代码:

public class DefinitionRecyclerAdapter extends BaseRecyclerViewAdapter {

public DefinitionRecyclerAdapter(Context mContext, List mDatas, PullToRefreshRecyclerView pullToRefreshRecyclerView) {
super(mContext, R.layout.item_pull_refresh, mDatas, pullToRefreshRecyclerView);
}

@Override
protected void convert(BaseViewHolder baseViewHolder, String s) {
baseViewHolder.setText(R.id.title,s);
}
}

①使用者需要在继承BaseRecyclerViewAdapter时传入一个数据实体类型,具体的操作在convert()方法中操作。
②BaseViewHolder提供了一些常用View的基本操作,通过baseViewHolder.getView()可得到布局中的控件。
(2)BaseRecyclerViewAdapter提供了两个构造函数

public BaseRecyclerViewAdapter(Context mContext, int mLayoutResId, List mDatas, PullToRefreshRecyclerView pullToRefreshRecyclerView) {
this.mContext = mContext;
this.mLayoutResId = mLayoutResId;
this.mDatas = mDatas;
this.mRecyclerView=pullToRefreshRecyclerView;
}

public BaseRecyclerViewAdapter(Context mContext, int mLayoutResId, List mDatas) {
this.mContext = mContext;
this.mLayoutResId = mLayoutResId;
this.mDatas = mDatas;
}

主要是对PullToRefreshRecyclerView和RecyclerView的适配,使用时适配器根据需要使用对应的构造函数。
2.添加Item的点击和长按事件

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(1) Item点击事件实现

itemClickAdapter.setOnItemLongClickListener(new BaseRecyclerViewAdapter.onItemLongClickListener() {
@Override
public boolean onItemLongClick(View view, int position) {
showToast(“进行长按操作”);
return true;
}
});

3.多布局的使用

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

BaseRecyclerViewAdapter的多布局实现需要注意的四步:
①自定义Adapter需要继承BaseRecyclerViewMultiItemAdapter。
② 数据实体类需要继承BaseMultiItemEntity,在getItemViewType()返回布局类型。
③ 在自定义Adapter中的构造函数中通过addItemType()传入不同类型对应的布局。
④在自定义Adapter中的convert进行类型判断,做相对应的操作。

public class MultipleAdapter extends BaseRecyclerViewMultiItemAdapter {

private int mHeight;

public MultipleAdapter(Context mContext, List mDatas) {
super(mContext, mDatas);
mHeight = DisplayUtil.dip2px(mContext, 100);
addItemType(MultiItem.TYPE_TEXT, R.layout.item_main);
addItemType(MultiItem.TYPE_IMG, R.layout.item_img);
addItemType(MultiItem.TYPE_TEXT_IMG, R.layout.item_click);
}

@Override
protected void convert(BaseViewHolder baseViewHolder, MultiItem multiItem) {
switch (baseViewHolder.getItemViewType()) {
case MultiItem.TYPE_TEXT:
baseViewHolder.getView(R.id.card_view).getLayoutParams().height = mHeight;
baseViewHolder.setText(R.id.title, multiItem.getTitle());
break;
case MultiItem.TYPE_IMG:
baseViewHolder.setImageResource(R.id.ivImg, multiItem.getRes());
break;
case MultiItem.TYPE_TEXT_IMG:
baseViewHolder.setImageResource(R.id.ivImg, multiItem.getRes());
baseViewHolder.setText(R.id.titleTv, multiItem.getTitle());
break;

}

}

4.添加拖拽、滑动删除

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

局限:只针对RecyclerView,对本框架封装的PullToRefreshRecyclerView会出现混乱。
①BaseRecyclerViewAdapter和BaseRecyclerViewMultiItemAdapter都已经封装支持拖拽、滑动,适配器只需要根据需求继承其中一个即可。
②框架提供了一个BaseRecycleItemTouchHelper,对于普通的左右滑动删除、拖拽已经实现,如果想自定义可以继承BaseRecycleItemTouchHelper类,再重写相对应的方法进行实现。
④在Activity/Fragment中需要实现以下代码:

ItemTouchHelper.Callback callback=new BaseRecycleItemTouchHelper(dragAndDeleteAdapter);
ItemTouchHelper itemTouchHelper=new ItemTouchHelper(callback);
itemTouchHelper.attachToRecyclerView(mRecyclerView);

⑤BaseRecyclerViewAdapter.OnDragAndDeleteListener进行操作动作完成之后的回调。

@Override
public void onMoveComplete() {
ToastUtils.showToast(this, “移动操作完成”);
}

@Override
public void onDeleteComplete() {
ToastUtils.showToast(this, “删除操作完成”);
}

四、MVP+RxJava+Retrofit的封装使用

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

1.在使用Retrofit请求网络之前需要进行配置,在框架中提供了了Config配置类
属性作用
DEBUG是否为BuildConfig.DEBUG,日志输出需要
CONTEXT设置Context,必填项
URL_DOMAIN网络请求的域名,需要以“/”结尾
URL_CACHE网络缓存地址,需要设置缓存才可以成功
MAX_CACHE_SECONDS设置OkHttp的缓存机制的最大缓存时间,默认为一天
MAX_MEMORY_SIZE缓存最大的内存,默认为10M
MClASS设置网络请求json通用解析类
EXPOSEPARAMJson数据某些字段在没有数据是会不返回来,可通过这个属性设置过滤
USER_CONFIGSharePreference保存的名称
CONNECT_TIMEOUT_SECONDS请求接口超时设定
READ_TIMEOUT_SECONDS请求接口超时设定
HEADERS设置Http全局请求头
SQLITE_DB_NAME数据库名称
SQLITE_DB_VERSION数据库版本名
在项目中需要根据项目需要进行配置,在Application中设置

private void config(){
//基本配置
Config.DEBUG= BuildConfig.DEBUG;
Config.CONTEXT=this;
//Retrofit配置
Config.URL_CACHE=AppConfig.URL_CACHE;
Config.MClASS= Result.class;
Config.URL_DOMAIN=“https://api.apiopen.top/”;
//SharePreference配置
Config.USER_CONFIG=“Collection_User”;
Config.SQLITE_DB_VERSION=0;
}

根据项目需要定义一个通用的数据实体类,这是本例通用实体类,这个类需要设置到Applicatin中

public class Result implements Serializable {

private int code;
private String msg;
private T newslist;

public int getCode() {
return code;
}

public void setCode(int code) {
this.code = code;
}

public String getMsg() {
return msg;
}

public void setMsg(String msg) {
this.msg = msg;
}

public T getNewslist() {
return newslist;
}

public void setNewslist(T newslist) {
this.newslist = newslist;
}
}

注意:由于每个项目返回来的json数据格式有所不同,如果Result中代表的字段例如newslist没有内容返回来的时候这个字段需要后台控制不返回,如果不做处理会报解析错误,可以通过设置Config.EXPOSEPARAM属性过滤字段。
2.RxJava+Retrofit+OkHttp

(1)RequestBuilder的设置(网络请求的配置)

属性作用
ReqType数据处理的方式,默认DEFAULT_CACHE_LIST,使用到OkHttp缓存的需要需要设置Config.URL_CACHE
NO_CACHE_MODEL不设置缓存,返回model
NO_CACHE_LIST不设置缓存,返回list
DEFAULT_CACHE_MODEL使用Okttp默认缓存,返回model
DEFAULT_CACHE_LIST使用Okttp默认缓存,返回list
DISK_CACHE_LIST_LIMIT_TIME限时使用自定义磁盘缓存,返回List
DISK_CACHE_MODEL_LIMIT_TIME限时使用自定义磁盘缓存,返回model
DISK_CACHE_NO_NETWORK_LIST自定义磁盘缓存,没有网络返回磁盘缓存,返回List
DISK_CACHE_NO_NETWORK_MODEL自定义磁盘缓存,没有网络返回磁盘缓存,返回Model
DOWNLOAD_FILE_MODEL文件下载模式,返回Model
HttpType网络请求方式,默认DEFAULT_GET
DEFAULT_GETGET请求
DEFAULT_POSTPOST请求
FIELDMAP_POST如果请求URL出现中文乱码,可选择这个
JSON_PARAM_POSTjson格式请求参数
ONE_MULTIPART_POST上传一张图片
MULTIPLE_MULTIPART_POST上传多张图片
DOWNLOAD_FILE_GET下载文件
ReqMode请求模式,默认ASYNCHRONOUS
ASYNCHRONOUS异步请求
SYNCHRONIZATION同步请求
其它参数
setTransformClass设置请求转化Class
setUrl设置请求url,如果不设置完全连接则会使用Config.URL_DOMIN进行拼接
setFilePathAndFileName设置自定义缓存时的路径和文件名
setLimtHours设置自定义缓存的有效时间
setHeader设置请求头
setHeaders设置请求头集合
setHttpTypeAndReqType设置请求数据类型和请求方式
setImagePath设置上传图片路径
setImagePaths设置多张图片路径
isUserCommonClass设置是否使用公用类转化
setReqMode设置同步异步

(2)使用模块

RequestBuilder<Result<List>> resultRequestBuilder = new RequestBuilder<>(new RxObservableListener<Result<List>>(mView) {
@Override
public void onNext(Result<List> result) {
mView.refreshUI(result.getResult());
}
});

resultRequestBuilder
.setUrl(ApiUrl.URL_WETCHAT_FEATURED)
.setTransformClass(WeChatNews.class)
.setParam(“page”,page)
.setParam(“type”,“video”)
.setParam(“count”,num);

rxManager.addObserver(DataManager.DataForHttp.httpRequest(resultRequestBuilder));

(3)Retrofit的扩展
如果存在DataManager提供的方法满足不了的请求可以通过RetrofitManager提供的getNoCacheApiService()和getApiService()获得不缓存和缓存的Retrofit,然后通过RxSubscriber进行回调。

Observable observable = RetrofitManager.getNoCacheApiService(ApiService.class)
.getWeChatStr(ApiUrl.URL_WECHAT_HOST + ApiUrl.ACCESS_TOKEN, reqParams);

DisposableObserver observer = observable
.compose(RxSchedulers.io_main())
.subscribeWith(new RxSubscriber() {
@Override
public void _onNext(WeChatAccessToken weChatAccessToken) {
getUserInfo(weChatAccessToken);
}

@Override
public void _onError(NetWorkCodeException.ResponseThrowable responseThrowable) {
showToast(R.string.wx_LoginResultEmpty);
hideLoadingDialog();
finish();
}

@Override
public void _onComplete() {

}
});

rxManager.addObserver(observer);

定义一个ApiService类

public interface ApiService {
/**

  • 微信精选
  • @param url
  • @param map
  • @return
    */
    @GET
    Observable<Result<List>> getWeChatFeaturedNews(@Url String url, @QueryMap Map<String,Object> map);
    }
注意:
(1)RxObservableListener有三个回调方法

void onNext(T result);
void onComplete();
void onError(NetWorkCodeException.ResponseThrowable e);

只会重写onNext方法,其它两个方法可以自行选择重写。
(2)RxObservableListener提供两个构造函数

protected RxObservableListener(BaseView view){
this.mView = view;
}

protected RxObservableListener(BaseView view, String errorMsg){
this.mView = view;
this.mErrorMsg = errorMsg;
}

这两个构造函数主要主要是为了统一处理onError的,如果要自定义错误提醒,则可以选择第二个构造函数。
(3)通过DataManager的网络请求方式会返回来一个DisposableObserver,需要把它通过rxManager.addObserver()添加进CompositeDisposable才能正常执行,方便对网络请求的销毁管理。
(4)如果项目没有统一的解析been类,那么Config的公用类就不用设置了,在Retrofit请求的时候直接setTransformClass指定一个解析类就可以了
(5)如果项目想两种方式共存,那么在请求的时候需要通过setUserCommonClass(false)设置才能不使用统一解析类进行解析
(6)请求的域名已经在Application设置好了,setUrl不需要填完整的url
②要区分清楚接口返回的数据时List还是Model,从而选择对应的ReqType
(7)setRequestParam可以设置参数集合,setParam可以单个设置
(8)使用DISK_CACHE_LIST_LIMIT_TIME/DISK_CACHE_MODEL_LIMIT_TIME这两个显示限时缓存时需要通过setFilePathAndFileName()设置保存路径setLimtHours()设置缓存时间(单位为:小时)
(9)如果要上传单张图片需要用到HttpType.ONE_MULTIPART_POST的请求方式,同时通过RequestBuilder设置MultipartBody.Part
3.MVP

1.写一个Contract类对Presenter和View进行统一管理(View需要实现BaseView,Presenter需要实现BasePresenter

public interface WeChatFeaturedContract {

interface View extends BaseView {
void refreshUI(List newsList);
}

abstract class Presenter extends BasePresenter {
public abstract void requestFeaturedNews(int page, int num);
}

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

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

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

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

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

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
img

如果你进阶的路上缺乏方向,可以加入我们的圈子和安卓开发者们一起学习交流!

  • Android进阶学习全套手册

    img

  • Android对标阿里P7学习视频

    img

  • BATJ大厂Android高频面试题

    img

最后,借用我最喜欢的乔布斯语录,作为本文的结尾:

人这一辈子没法做太多的事情,所以每一件都要做得精彩绝伦。
你的时间有限,所以不要为别人而活。不要被教条所限,不要活在别人的观念里。不要让别人的意见左右自己内心的声音。
最重要的是,勇敢的去追随自己的心灵和直觉,只有自己的心灵和直觉才知道你自己的真实想法,其他一切都是次要。

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
img
、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-QkfpdV1V-1712692375288)]

如果你进阶的路上缺乏方向,可以加入我们的圈子和安卓开发者们一起学习交流!

  • Android进阶学习全套手册

    [外链图片转存中…(img-C90Di27z-1712692375289)]

  • Android对标阿里P7学习视频

    [外链图片转存中…(img-HaSq5BjW-1712692375289)]

  • BATJ大厂Android高频面试题

    [外链图片转存中…(img-YOfmMnPD-1712692375289)]

最后,借用我最喜欢的乔布斯语录,作为本文的结尾:

人这一辈子没法做太多的事情,所以每一件都要做得精彩绝伦。
你的时间有限,所以不要为别人而活。不要被教条所限,不要活在别人的观念里。不要让别人的意见左右自己内心的声音。
最重要的是,勇敢的去追随自己的心灵和直觉,只有自己的心灵和直觉才知道你自己的真实想法,其他一切都是次要。

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-SdK66ceo-1712692375289)]

  • 9
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值