上面我们已经通过XML方式成功将fragment嵌入到Activity中(这种嵌入方式我们称为静态添加),但这种添加方式依然不够灵活.于是Android提供了另一种更加灵活的添加方式,也是我们日常开发中用的最多的一种添加方式----动态添加。
动态添加顾名思义就是在程序运行时根据不同的情况动态地添加我们所需的fragment,下面我们将通过实例来学习如何动态添加Fragment。
自定义一个AddByDynamicActivity,并根据屏幕宽高的变化,动态添加AddFragmentFirst和AddFragmentSecond,代码如下:
public class AddByDynamicActivity extends Activity {
@SuppressLint(“NewApi”)
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_addbydynamic);
//获取屏幕宽高
DisplayMetrics mDisplayMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(mDisplayMetrics);
//1.根据上下文获取FragmentManager对象
FragmentManager manager = this.getFragmentManager();
if(mDisplayMetrics.widthPixels < mDisplayMetrics.heightPixels){
//2.使用获取到的manager对象开启一个事务
FragmentTransaction mFragmentTransaction01 = manager.beginTransaction();
//3.替换Fragment
mFragmentTransaction01.replace(R.id.container, new AddFragmentFirst());
//4.提交事务
mFragmentTransaction01.commit();
}else{
FragmentTransaction mFragmentTransaction02 = manager.beginTransaction();
mFragmentTransaction02.replace(R.id.container, new AddFragmentSecond()).commit();
}
}
}
AddByDynamicActivity的布局文件activity_addbydynamic的代码如下:
<LinearLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:id=“@+id/container”
android:orientation=“horizontal”
/>
AddFragmentFirst和AddFragmentSecond的代码沿用上面学习静态添加Fragment时的代码。编写完成后,我们运行一下程序,显示的运行界面如下图所示:
然后点击Ctrl + F11组合键(这里使用Android模拟器测试),程序显示的界面如下图所示:
根据上面的运行结果我们可知,我们已经成功完成了根据不同的情况动态地添加或替换不同的Fragment对象的测试。接下来我们就此来深入的学习一下动态添加Fragment的具体编码步骤以及需要注意的地方。
首先,要在Activity管理Fragment,我们需要使用Android API提供的FragmentManager类,我们可以通过Activity的getFragmentManager()方法获取FragmentManager对象,而要对Fragment进行添加、删除、隐藏或替换操作,则需对每个需要执行的操作开启一个事务(FragmentTransaction),我们可以通过FragmentManager对象的beginTransaction()方法开启。接着,我们可以根据具体需求使用add()、remove()、hide()或replace()方法来执行对Fragment的管理操作。最后,我们必须将每一个事务都提交到事务管理器中等待执行,Android提供了commit()和commitAllowingStateLoss()两个方法来执行事务提交,两个方法的区别是当使用commit()方法时,宿主Activity必须在保存它的状态(用户离开Activity)之前执行commit()提交操作,否则将会抛出一个异常,这是因为当activity再次被恢复时commit之后的状态将丢失。如果丢失也没关系,那么使用commitAllowingStateLoss()方法即可。这里需要注意的是,当事务提交完成后,事务并不会马上执行,而是由事务管理器安排的队列执行顺序,在必要时在宿主Activity的UI线程中执行。若不想等待要马上执行的话,可以在UI线程调用executePendingTransactions()来立即执行由commit()提交的事务. 但这么做通常不必要,除非事务是其他线程中的任务的一个从属。
依据上面的代码实例,我们下面来总结一下动态添加Fragment的具体步骤:
1、使用Activity的上下文对象的getFragmentManager()方法获取FragmentManager对象;
2、使用获取到的FragmentManager对象开启一个事务(FragmentTransaction),调用beginTransaction()方法执行事务开启;
3、使用开启的事务对象(FragmentTransaction)执行添加、隐藏、删除或替换(add()、hide()、remove() or replace())Fragment的操作;
4、调用commit()或commitAllowingStateLoss()方法执行事务的提交。
在一些情况下,Fragment需要建立与宿主Activity或和同一宿主Activity的姊妹Fragment的通信来完成相关操作功能。Fragment一般可以调用getActivity()方法来访问宿主Activity实例,同样的,宿主Activity也可以调用FragmentManager类中的findFragmentById()或findFramentByTag()方法来得到Fragment的引用对象,并依此来调用Fragment中的方法。而要建立两个属于同一个Activity的Fragment对象的通信,则最好的方法就是借助宿主Activity这个桥梁来完成信息的传递,就比如上面讲的那个列表新闻显示的例子(FragmentA显示新闻标题列表,FragmentB显示新闻的详细内容),当我们点击FragmentA中的列表选项时,则需要将列表选项的值传递给FragmentB,以便FragmentB根据用户点击的列表项显示对应的新闻内容。下面我们将使用代码简单地实现新闻显示这个例子,加深对Fragment的了解。我们先整理一下实现思路:首先定义一个宿主Activity(例子名中为ActivityForCallBackActivity),主要用于装载新闻列表的Fragment(例子中名为ActivityForCallBackFragmentA)和显示新闻内容详细的Fragment(例子中名为ActivityForCallBackFragmentB),然后在ActivityForCallBackFragmentA中定义一个事件回调方法,并在宿主ActivityForCallBackActivity实现其回调接口,这里使用回调主要作用是用于监听用户点击新闻列表项的事件,接着在宿主ActivityForCallBackActivity使用getFragmentManager().findFragmentById()方法获取显示新闻内容详细(ActivityForCallBackFragmentB)的实例,并在实现的回调方法onArticleSelected()方法中调用ActivityForCallBackFragmentB中的setContent()用于设置从ActivityForCallBackFragmentA传递过来的新闻Title。好了,基本思路已经理清,下面看下具体实现的代码。
首先定义一个ActivityForCallBackActivity类作为两个Fragment的宿主Activity,该类的代码如下:
/**
-
@author AndroidLeaf
-
该示例主要实现新闻阅读例子
*/
public class ActivityForCallBackActivity extends Activity implements OnArticleSelectedListener{
private ActivityForCallBackFragmentB mFragmentB;
@SuppressLint(“NewApi”)
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_activityforcallback);
//获取ActivityForCallBackFragmentB新闻详细内容对象
mFragmentB = (ActivityForCallBackFragmentB)
getFragmentManager().findFragmentById(R.id.activityforcallback_fragment_b);
}
@Override
public void onArticleSelected(int itemID,String title) {
// TODO Auto-generated method stub
//详细页显示新闻Title
mFragmentB.setContent(title);
}
}
布局文件activity_activityforcallback.xml的代码如下:
<LinearLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:orientation=“horizontal”
<fragment
android:layout_width=“0dp”
android:layout_weight=“1”
android:layout_height=“match_parent”
android:id=“@+id/activityforcallback_fragment_a”
android:name=“com.androidleaf.fragmentdemo.fragment.ActivityForCallBackFragmentA”
/>
<fragment
android:layout_width=“0dp”
android:layout_weight=“2”
android:layout_height=“match_parent”
android:id=“@+id/activityforcallback_fragment_b”
android:name=“com.androidleaf.fragmentdemo.fragment.ActivityForCallBackFragmentB”
/>
然后创建展示新闻列表的ActivityForCallBackFragmentA.java类,代码如下:
public class ActivityForCallBackFragmentA extends Fragment implements OnItemClickListener{
private ListView mListView;
ArrayAdapter adapter = null;
ArrayList list = null;
/**
- 创建一个事件回调函数,用来监听用户点击列表选项的操作
*/
public OnArticleSelectedListener mArticleSelectedListener;
public interface OnArticleSelectedListener{
public void onArticleSelected(int itemID,String title);
}
@Override
public void onAttach(Activity activity) {
// TODO Auto-generated method stub
super.onAttach(activity);
try {
//将宿主Activity对象强制转换成OnArticleSelectedListener实例,主要是为了确保宿主Activity实现监听接口
mArticleSelectedListener = (OnArticleSelectedListener) activity;
} catch (ClassCastException e) {
// TODO: handle exception
throw new ClassCastException(activity.toString() + " must implement OnArticleSelectedListener");
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
/**
- 初始化新闻列表的值
*/
list = new ArrayList();
list.add(“a”);
list.add(“b”);
list.add(“c”);
list.add(“d”);
list.add(“e”);
list.add(“f”);
list.add(“g”);
adapter = new ArrayAdapter(getActivity(), android.R.layout.simple_list_item_1, android.R.id.text1, list);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// TODO Auto-generated method stub
View inView = inflater.inflate(R.layout.fragment_activityforcallback_a, container, false);
mListView = (ListView)inView.findViewById(R.id.callback_listview);
mListView.setAdapter(adapter);
mListView.setOnItemClickListener(this);
return inView;
}
@Override
public void onPause() {
// TODO Auto-generated method stub
super.onPause();
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position,
long id) {
// TODO Auto-generated method stub
//将选中的列表选项ID和Title传递给实现监听接口的宿主Activity
mArticleSelectedListener.onArticleSelected(position,list.get(position));
}
}
布局文件fragment_activityforcallback_a.xml的代码如下:
<RelativeLayout xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:id=“@+id/callback_a_container”
android:paddingBottom=“@dimen/activity_vertical_margin”
android:paddingLeft=“@dimen/activity_horizontal_margin”
android:paddingRight=“@dimen/activity_horizontal_margin”
android:paddingTop=“@dimen/activity_vertical_margin”
<ListView
android:layout_width=“match_parent”
android:id=“@+id/callback_listview”
android:layout_height=“wrap_content”
/>
接着创建显示详细新闻内容的ActivityForCallBackFragmentB.java类,代码如下:
public class ActivityForCallBackFragmentB extends Fragment{
private TextView mTextView;
@Override
public void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// TODO Auto-generated method stub
View inView = inflater.inflate(R.layout.fragment_activityforcallback_b, container, false);
mTextView = (TextView)inView.findViewById(R.id.callback_textview);
return inView;
}
@Override
public void onPause() {
// TODO Auto-generated method stub
super.onPause();
}
public void setContent(String titleContent){
if(!TextUtils.isEmpty(titleContent)){
mTextView.setText(titleContent);
}
}
}
布局文件fragment_activityforcallback_b.xml代码如下:
<RelativeLayout xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:background=“#FF0000”
android:paddingBottom=“@dimen/activity_vertical_margin”
android:paddingLeft=“@dimen/activity_horizontal_margin”
android:paddingRight=“@dimen/activity_horizontal_margin”
android:paddingTop=“@dimen/activity_vertical_margin”
<TextView
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_centerInParent=“true”
android:textSize=“50sp”
android:textColor=“#000000”
android:id=“@+id/callback_textview”
/>
代码编写完成,接下来运行程序,效果图如下图示:
在Fragment中添加ActionBar
在Fragment添加ActionBar的方法其实和在Activity添加ActionBar的方法类似,在这里就不再赘述,需要了解ActionBar使用方法的读者,可以学习(ActionBar简介和使用实例)这篇文章。不过读者使用时需要注意的一点是,当用户在Fragment中选择一个菜单项时,其宿主Activity会首先接收到对应的回调.如果Activity的on-item-selected回调函数实现并没有处理被选中的项目, 然后事件才会被传递到Fragment的回调。在文章末尾提供下载的Demo中有关于在Fragment添加ActionBar的具体例子,由于较为简单,这里就不贴出代码,读者可以下载Demo来进行学习。
==================================================================================
Fragment起初是专门为了应用程序适配手机小屏幕和平板电脑大屏幕以达到应用在两者上运行能有最好的界面显示效果而设计的,例如对于屏幕尺寸较大的平板电脑,它有比一般手机设备更大的空间来显示更多的视图控件对象,那系统在运行应用时,当检测到是运行在较大屏幕尺寸的设备上时,系统将自动加载更大的屏幕界面布局,然后可以动态地添加Fragment来填充剩余的视图空间,最后实现更多的界面交互。不过由于Fragment有自己独立的生命周期,并可以在Activity中动态地被添加、删除或替换的特性,许多开发者在开发手机应用时也喜欢频繁使用Fragment来实现更加灵活的UI视图(即便应用不考虑或不需要在平板电脑上很好的显示界面)。下面我们通过一个新闻列表显示实例来学习如何使用Fragment来实现手机和平板的的适配,首先创建一个宿主Activity(例子中名为AdapterMobileAndPadActivity),主要用于装载Fragment和显示视图布局,代码如下:
public class AdapterMobileAndPadActivity extends Activity{
@SuppressLint(“NewApi”)
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_adaptermobile_and_pad);
}
}
布局文件activity_adaptermobile_and_pad分为两处地方,一个是在res/layout文件路径下,另一个是res/layout-land文件路径下,如下图所示:
res/layout/activity_adaptermobile_and_pad布局文件代码如下:
<LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:orientation=“horizontal” >
<fragment
android:layout_width=“0dp”
android:layout_weight=“1”
android:layout_height=“match_parent”
android:id=“@+id/adapter_article_list”
android:name=“com.androidleaf.fragmentdemo.fragment.AdapterArticleListFragment”
/>
res/layout-land/activity_adaptermobile_and_pad布局文件代码如下:
<LinearLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:orientation=“horizontal”
<fragment
android:layout_width=“0dp”
android:layout_weight=“1”
android:layout_height=“match_parent”
android:id=“@+id/adapter_article_list”
android:name=“com.androidleaf.fragmentdemo.fragment.AdapterArticleListFragment”
/>
<FrameLayout
android:layout_width=“0dp”
android:layout_weight=“3”
android:layout_height=“match_parent”
android:id=“@+id/details_container”
/>
两个文件夹中的同名布局文件分别为手机和平板界面的显示提供视图布局,当应用运行时,Android系统会根据当前运行环境来判断应用是否运行在大屏幕设备上,如果运行在大屏幕设备上,则加载res/layout-land下的布局文件,否则默认加载res/layout下的布局文件。关于动态加载界面布局的更多内容,可以学习( Android官方提供的支持不同屏幕大小的全部方法 )这篇文章。
接下来创建新闻列表显示界面的Fragment(例子中名为AdapterArticleListFragment),具体的代码如下:
public class AdapterArticleListFragment extends ListFragment{
/**
- 判断当前加载的是否为大屏幕布局
*/
private boolean isScreenPad;
/**
- 用来记录上次选中的项
*/
private int mCurrentPosition;
/**
- 列表测试数据
*/
public static String[] articleTitles = {
“a”,“b”,“c”,“d”,“e”,“f”,“g”,
};
@Override
public void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onActivityCreated(savedInstanceState);
//绑定数据列表
setListAdapter(new ArrayAdapter<>(getActivity(), android.R.layout.simple_list_item_1, android.R.id.text1, articleTitles));
View details = getActivity().findViewById(R.id.details_container);
//检测是否使用大屏幕尺寸的布局
isScreenPad = details != null && details.getVisibility() == View.VISIBLE;
if(savedInstanceState != null){
//获取上次离开界面时列表选项值
mCurrentPosition = savedInstanceState.getInt(“currentChoice”, 0);
}
if(isScreenPad){
//设置列表选中的选项高亮
getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
showDetails(mCurrentPosition);
}
}
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
// TODO Auto-generated method stub
super.onListItemClick(l, v, position, id);
showDetails(position);
}
/**
- 离开界面时保存当前选中的选项的状态值
*/
@Override
public void onSaveInstanceState(Bundle outState) {
// TODO Auto-generated method stub
super.onSaveInstanceState(outState);
outState.putInt(“currentChoice”, mCurrentPosition);
}
/**
-
@param index
*/
public void showDetails(int index){
mCurrentPosition = index;
if(isScreenPad){
getListView().setItemChecked(index, true);
AdapterArticleDetailFragment mDetailFragment = (AdapterArticleDetailFragment) getActivity().getFragmentManager().findFragmentById(R.id.details_container);
//若mDetailFragment为空或选中非当前显示的Fragment
if(mDetailFragment == null || mDetailFragment.showIndex() != index){
mDetailFragment = AdapterArticleDetailFragment.newInstance(index);
//替换Fragment实例对象
getActivity().getFragmentManager().beginTransaction().replace(R.id.details_container, mDetailFragment)
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
.commit();
}
}else{
Intent mIntent = new Intent();
mIntent.setClass(getActivity(), AdapterArticleDetailActivity.class);
Bundle mBundle = new Bundle();
mBundle.putInt(“index”, index);
mIntent.putExtras(mBundle);
getActivity().startActivity(mIntent);
}
}
@Override
public void onPause() {
// TODO Auto-generated method stub
super.onPause();
}
}
然后创建新闻内容详细显示的Fragment(例子中名为AdapterArticleDetailFragment),具体的代码如下:
public class AdapterArticleDetailFragment extends Fragment{
private TextView titleContent;
public static int index;
/**
-
实例化 AdapterArticleDetailFragment对象
-
@param index
-
@return AdapterArticleDetailFragment
*/
public static AdapterArticleDetailFragment newInstance(int index){
AdapterArticleDetailFragment mAdapterArticleDetailFragment = new AdapterArticleDetailFragment();
Bundle mBundle = new Bundle();
mBundle.putInt(“index”, index);
//保存当前选中的选项ID
mAdapterArticleDetailFragment.setArguments(mBundle);
return mAdapterArticleDetailFragment;
}
/**
-
获取当前显示的选项ID
-
@return int index
*/
public int showIndex(){
if(getArguments() == null){
return 0;
}
return getArguments().getInt(“index”,0);
}
@Override
public void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// TODO Auto-generated method stub
View inView = inflater.inflate(R.layout.fragment_articledetails, container, false);
titleContent = (TextView)inView.findViewById(R.id.articledetails_textview);
//设置详情页的内容
titleContent.setText(AdapterArticleListFragment.articleTitles[getArguments().getInt(“index”,0)]);
return inView;
}
@Override
public void onPause() {
// TODO Auto-generated method stub
super.onPause();
}
}
布局文件fragment_articledetails的代码如下:
<RelativeLayout xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:background=“#FF0000”
android:paddingBottom=“@dimen/activity_vertical_margin”
android:paddingLeft=“@dimen/activity_horizontal_margin”
android:paddingRight=“@dimen/activity_horizontal_margin”
android:paddingTop=“@dimen/activity_vertical_margin”
<TextView
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_centerInParent=“true”
android:textSize=“55sp”
android:textColor=“#000000”
android:id=“@+id/articledetails_textview”
/>
学习福利
【Android 详细知识点思维脑图(技能树)】
其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。
虽然 Android 没有前几年火热了,已经过去了会四大组件就能找到高薪职位的时代了。这只能说明 Android 中级以下的岗位饱和了,现在高级工程师还是比较缺少的,很多高级职位给的薪资真的特别高(钱多也不一定能找到合适的),所以努力让自己成为高级工程师才是最重要的。
这里附上上述的面试题相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司19年的面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。
由于篇幅有限,这里以图片的形式给大家展示一小部分。
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化学习资料的朋友,可以戳这里获取
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
android:paddingTop=“@dimen/activity_vertical_margin”
<TextView
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_centerInParent=“true”
android:textSize=“55sp”
android:textColor=“#000000”
android:id=“@+id/articledetails_textview”
/>
学习福利
【Android 详细知识点思维脑图(技能树)】
[外链图片转存中…(img-whnIvr13-1714198305724)]
其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。
虽然 Android 没有前几年火热了,已经过去了会四大组件就能找到高薪职位的时代了。这只能说明 Android 中级以下的岗位饱和了,现在高级工程师还是比较缺少的,很多高级职位给的薪资真的特别高(钱多也不一定能找到合适的),所以努力让自己成为高级工程师才是最重要的。
这里附上上述的面试题相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司19年的面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。
由于篇幅有限,这里以图片的形式给大家展示一小部分。
[外链图片转存中…(img-8EmRrgI4-1714198305724)]
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化学习资料的朋友,可以戳这里获取
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!