仿qq底部tab导航
本篇博客主要实现以下效果:
- 使用FragmentTabHost实现qq底部Tab切换
- 使用RadioGroup和RadioButton实现仿qq底部切换
- 使用RadioGroup和ViewPager 实现可以滑动切换的仿qq底部Tab切换
- 解决Fragment多次实例化的几种方案
- Fragemnt的懒加载(网上很多人称之为Fragemnt的最优加载)
效果图
老规矩,废话 不多说,先看效果图
FragmentTabHost实现qq底部Tab实践的效果图
RadioGroup和ViewPager 实现可以滑动切换的仿qq底部Tab效果图
使用FragmentTabHost实现qq底部Tab切换
第一步先看布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!--用来填充Fragemnt的FrayLayout-->
<FrameLayout
android:id="@+id/main_layout_content"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
</FrameLayout>
<!--FragmentTabHost-->
<android.support.v4.app.FragmentTabHost
android:id="@android:id/tabhost"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#F6F6F6"
android:paddingBottom="5dp"
android:paddingTop="5dp">
<FrameLayout
android:id="@android:id/tabcontent"
android:layout_width="0dp"
android:layout_height="0dp"/>
</android.support.v4.app.FragmentTabHost>
</LinearLayout>
其实很简单,没什么好说的 ,就是一个vertical的LinearLayout中放置着一个FrameLayout和FragmentTabHost
接下来我们来看一下代码
public class FirstStyleActivity extends AppCompatActivity {
FragmentTabHost mTabHost;
private TabWidget mTabWidget;
private List<FragmentInfo> mFragmentEntities;
private static final String TAG = "xujun";
private static final String tag = "tag";
public static final String[] mTiltles = new String[]{
"首页", "课程", "直播", "个人"
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_first_style);
mTabHost = (FragmentTabHost) findViewById(android.R.id.tabhost);
mTabHost.setup(this, getSupportFragmentManager(), R.id.main_layout_content);
mTabWidget = mTabHost.getTabWidget();
// 去掉分割线
mTabWidget.setDividerDrawable(null);
mFragmentEntities = MainFragmentFactory.getInstance().getList();
initListener();
initData();
}
private void initData() {
int size = mFragmentEntities.size();
for (int i = 0; i < size; i++) {
Log.i(TAG, "size:=" + size);
FragmentInfo fragmentInfo = mFragmentEntities.get(i);
String title = fragmentInfo.getTitle();
TabHost.TabSpec tabSpec = mTabHost.newTabSpec(title).setIndicator(getTabView
(i));
Bundle bundle = new Bundle();
bundle.putString(tag, mTiltles[i]);
mTabHost.addTab(tabSpec, fragmentInfo.getClz(), bundle);
}
updateTab(0);
}
private void initListener() {
mTabHost.setOnTabChangedListener(new TabHost.OnTabChangeListener() {
@Override
public void onTabChanged(String tabId) {
int currentTab = mTabHost.getCurrentTab();
Log.i(TAG, "onTabChanged:currentTab:=" + currentTab);
updateTab(currentTab);
}
});
}
private View getTabView(int i) {
View view = View.inflate(this, R.layout.tab_layout, null);
int currentTab = mTabHost.getCurrentTab();
Log.i(TAG, "currentTab:=" + currentTab);
setSingleView(view, currentTab, i);
return view;
}
private void setSingleView(View view, int currentTab, int index) {
FragmentInfo fragmentInfo = mFragmentEntities.get(index);
int[] imagIds = fragmentInfo.getImagIds();
int[] colors = fragmentInfo.getColors();
TextView tv = (TextView) view.findViewById(R.id.tab_tv);
ImageView iv = (ImageView) view.findViewById(R.id.tab_icon);
tv.setText(fragmentInfo.getTitle());
Resources resources = getResources();
if (index == currentTab) {
tv.setTextColor(resources.getColor(colors[1]));
iv.setImageDrawable(resources.getDrawable(imagIds[1]));
} else {
tv.setTextColor(getResources().getColor(colors[0]));
iv.setImageDrawable(resources.getDrawable(imagIds[0]));
}
}
private void updateTab(int currentTab) {
int childCount = mTabWidget.getChildCount();
for (int i = 0; i < childCount; i++) {
View view = mTabWidget.getChildTabViewAt(i);
setSingleView(view, currentTab, i);
}
}
}
其实说起来也很简单,主要分为以下步骤
- 第一步,实例化FragmentTabHost并设置相关样式
mTabHost = (FragmentTabHost) findViewById(android.R.id.tabhost);
mTabHost.setup(this, getSupportFragmentManager(), R.id.main_layout_content);
mTabWidget = mTabHost.getTabWidget();
// 去掉分割线
mTabWidget.setDividerDrawable(null);
- 第二步,添加每一个TabHost
for (int i = 0; i < size; i++) {
Log.i(TAG, "size:=" + size);
FragmentInfo fragmentInfo = mFragmentEntities.get(i);
String title = fragmentInfo.getTitle();
TabHost.TabSpec tabSpec = mTabHost.newTabSpec(title).setIndicator(getTabView
(i));
Bundle bundle = new Bundle();
bundle.putString(tag, mTiltles[i]);
mTabHost.addTab(tabSpec, fragmentInfo.getClz(), bundle);
}
- 第三步,通过设置 监听器来实现底部tab颜色和图案样式的转换
mTabHost.setOnTabChangedListener(new TabHost.OnTabChangeListener() {
@Override
public void onTabChanged(String tabId) {
int currentTab = mTabHost.getCurrentTab();
Log.i(TAG, "onTabChanged:currentTab:=" + currentTab);
updateTab(currentTab);
}
});
运行上述代码及可以看到如下效果图
使用RadioGroup和RadioButton实现仿qq底部切换
第一步 ,先看布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:id="@+id/activity_second_style"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<!--用来填充Fragemnt的FrayLayout-->
<FrameLayout
android:id="@+id/fl"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
</FrameLayout>
<!--使用RadioGroup来实现tab的切换-->
<RadioGroup
android:id="@+id/rg"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
<RadioButton
android:id="@+id/rb_home"
style="@style/bottom_tab"
android:drawableTop="@drawable/sel_home"
android:text="首页"/>
<RadioButton
android:id="@+id/rb_course"
style="@style/bottom_tab"
android:drawableTop="@drawable/sel_course"
android:text="课程"/>
<RadioButton
android:id="@+id/rb_direct_seeding"
style="@style/bottom_tab"
android:drawableTop="@drawable/sel_direct_seeding"
android:text="直播"/>
<RadioButton
android:id="@+id/rb_me"
style="@style/bottom_tab"
android:drawableTop="@drawable/sel_me"
android:text="我的"/>
</RadioGroup>
</LinearLayout>
其实每一个tab的选中时利用RadioGroup中RadioButton的互相排斥的特性,即每一次只能选中一个 RadioButton
至于bottom_tab的style,只不过是将相同的arr提取出来,减少布局的代码量和方便统一修改而已,平时我们在写布局代码 的时候也可以这样
<style name="bottom_tab">
<item name="android:layout_width" >0dp</item>
<item name="android:layout_height" >wrap_content</item>
<item name="android:layout_weight" >1</item>
<item name="android:text" >0dp</item>
<item name="android:gravity" >center</item>
<item name="android:textColor" >@drawable/sel_bottom_tab_text</item>
<item name="android:padding" >8dp</item>
<item name="android:button" >@null</item>
</style>
第二步,我们来看一下Activity的 代码
public class ThreeActivity extends AppCompatActivity {
FrameLayout mFl;
RadioGroup mRg;
private FragmentManager mFragmentManager;
private int position = 0;
public static final String[] mTiltles = new String[]{
"首页", "课程", "直播", "个人"
};
private List<Fragment> mFragments;
private Fragment mCurFragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_three);
mFl = (FrameLayout) findViewById(R.id.fl);
mRg = (RadioGroup) findViewById(R.id.rg);
mFragments = new ArrayList<>();
for (int i = 0; i < mTiltles.length; i++) {
ItemFragement itemFragement = ItemFragement.newInstance(mTiltles[i]);
mFragments.add(itemFragement);
}
mCurFragment = mFragments.get(position);
replaceFragment(mCurFragment);
((RadioButton)mRg.getChildAt(position)).setChecked(true);
initListener();
}
private void initListener() {
mRg.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
RadioButton radioButton = (RadioButton) group.findViewById(checkedId);
if (false == radioButton.isChecked()) {
return;
}
switch (checkedId) {
case R.id.rb_home:
position = 0;
break;
case R.id.rb_course:
position = 1;
break;
case R.id.rb_direct_seeding:
position = 2;
break;
case R.id.rb_me:
position = 3;
break;
default:
position = 0;
break;
}
LUtils.i("position==" + position);
Fragment to = mFragments.get(position);
showFragment(mCurFragment, to);
mCurFragment = to;
}
});
}
private void showFragment(Fragment from, Fragment to) {
FragmentManager supportFragmentManager = getSupportFragmentManager();
FragmentTransaction transaction = supportFragmentManager.beginTransaction();
if (!to.isAdded()) { // 先判断是否被add过
transaction.hide(from).add(R.id.fl, to).commit(); // 隐藏当前的fragment,add下一个到Activity中
} else {
transaction.hide(from).show(to).commit(); // 隐藏当前的fragment,显示下一个
}
}
/**
* 这个方法用老替换fragment
* xujun
* 2016/5/3 17:28.
*/
private void replaceFragment(Fragment fragmeny) {
FragmentManager supportFragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = supportFragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fl, fragmeny).commit();
}
}
思路解析
- 实例化各个控件,这里代码就不贴出来了
- 初始化 Fragemnt 和选中各个tab
mFragments = new ArrayList<>();
for (int i = 0; i < mTiltles.length; i++) {
ItemFragement itemFragement = ItemFragement.newInstance(mTiltles[i]);
mFragments.add(itemFragement);
}
mCurFragment = mFragments.get(position);
replaceFragment(mCurFragment);
((RadioButton)mRg.getChildAt(position)).setChecked(true);
private void replaceFragment(Fragment fragmeny) {
FragmentManager supportFragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = supportFragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fl, fragmeny).commit();
}
- 第三步,通过监听RadioGroup的 OnCheckedChangeListener事件,来实现tab和Fragemnt的切换
mRg.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
RadioButton radioButton = (RadioButton) group.findViewById(checkedId);
if (false == radioButton.isChecked()) {
return;
}
switch (checkedId) {
case R.id.rb_home:
position = 0;
break;
case R.id.rb_course:
position = 1;
break;
case R.id.rb_direct_seeding:
position = 2;
break;
case R.id.rb_me:
position = 3;
break;
default:
position = 0;
break;
}
LUtils.i("position==" + position);
Fragment to = mFragments.get(position);
showFragment(mCurFragment, to);
mCurFragment = to;
}
});
使用RadioGroup和ViewPager 实现可以滑动切换的仿qq底部Tab切换
第一步,我们 同样先看布局代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:id="@+id/activity_second_style"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<!--用来填充Fragemnt的ViewPager-->
<android.support.v4.view.ViewPager
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
</android.support.v4.view.ViewPager>
<!--使用RadioGroup来实现tab的切换-->
<RadioGroup
android:id="@+id/rg"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
<RadioButton
android:id="@+id/rb_home"
style="@style/bottom_tab"
android:drawableTop="@drawable/sel_home"
android:text="首页"/>
<RadioButton
android:id="@+id/rb_course"
style="@style/bottom_tab"
android:drawableTop="@drawable/sel_course"
android:text="课程"/>
<RadioButton
android:id="@+id/rb_direct_seeding"
style="@style/bottom_tab"
android:drawableTop="@drawable/sel_direct_seeding"
android:text="直播"/>
<RadioButton
android:id="@+id/rb_me"
style="@style/bottom_tab"
android:drawableTop="@drawable/sel_me"
android:text="我的"/>
</RadioGroup>
</LinearLayout>
第二步,我们一起来看一下Activity代码
public class SecondStyleActivity extends AppCompatActivity {
public static final String[] mTiltles = new String[]{
"首页", "课程", "直播", "个人"
};
private List<Fragment> mFragments;
ViewPager mViewPager;
RadioGroup mRg;
private int position = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second_style);
mViewPager = (ViewPager) findViewById(R.id.viewPager);
mRg = (RadioGroup) findViewById(R.id.rg);
initListener();
initData();
}
private void initData() {
mFragments = new ArrayList<>();
for (int i = 0; i < mTiltles.length; i++) {
ItemFragement itemFragement = ItemFragement.newInstance(mTiltles[i]);
mFragments.add(itemFragement);
}
BaseFragmentAdapter fragmentAdapter = new BaseFragmentAdapter
(getSupportFragmentManager(), mFragments, mTiltles);
mViewPager.setAdapter(fragmentAdapter);
mViewPager.setCurrentItem(position);
((RadioButton) mRg.getChildAt(position)).setChecked(true);
}
private void initListener() {
mViewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
@Override
public void onPageSelected(int position) {
RadioButton radioButton = (RadioButton) mRg.getChildAt(position);
radioButton.setChecked(true);
}
});
mRg.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
RadioButton rb = (RadioButton) group.findViewById(checkedId);
if (!rb.isChecked()) {
return;
}
switch (checkedId) {
case R.id.rb_home:
position = 0;
break;
case R.id.rb_course:
position = 1;
break;
case R.id.rb_direct_seeding:
position = 2;
break;
case R.id.rb_me:
position = 3;
break;
default:
position = 0;
break;
}
mViewPager.setCurrentItem(position);
}
});
}
}
思路解析如下
- 实例化ViewPager和RadioGroup
mViewPager = (ViewPager) findViewById(R.id.viewPager);
mRg = (RadioGroup) findViewById(R.id.rg);
- 第二步,初始化ViewPager的适配器和选中 那个tab
mFragments = new ArrayList<>();
for (int i = 0; i < mTiltles.length; i++) {
ItemFragement itemFragement = ItemFragement.newInstance(mTiltles[i]);
mFragments.add(itemFragement);
}
BaseFragmentAdapter fragmentAdapter = new BaseFragmentAdapter
(getSupportFragmentManager(), mFragments, mTiltles);
mViewPager.setAdapter(fragmentAdapter);
mViewPager.setCurrentItem(position);
((RadioButton) mRg.getChildAt(position)).setChecked(true);
- 第三步,监听ViewPager 的滑动事件和RadioGroup的OnCheckedChangeListener事件,分别切换到相应的 Fragemnt 和同步ViewPager中 position 与RadioGroup之间的联系
mViewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
@Override
public void onPageSelected(int position) {
RadioButton radioButton = (RadioButton) mRg.getChildAt(position);
radioButton.setChecked(true);
}
});
mRg.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
RadioButton rb = (RadioButton) group.findViewById(checkedId);
if (!rb.isChecked()) {
return;
}
switch (checkedId) {
case R.id.rb_home:
position = 0;
break;
case R.id.rb_course:
position = 1;
break;
case R.id.rb_direct_seeding:
position = 2;
break;
case R.id.rb_me:
position = 3;
break;
default:
position = 0;
break;
}
mViewPager.setCurrentItem(position);
}
});
注意事项
我们可以通过以下方法设置ViewPager左右页面 能缓存的fragment 数量
// 设置左右页面 能缓存的fragment 数量
mViewPager.setOffscreenPageLimit(fragmentAdapter.getCount() - 1);
到此仿qq底部tab切换的集中方法已经讲解完毕,之所以讲解了 三种方法,是想让大家了解多种实现方式,因为每一个人的习惯都不一样,有些人习惯使用与第一种方式,有人习惯使用第二种方式 。。。。。。了解多种 实现方式以后,我们要读懂别人的代码也容易得多了,其实我们还可以使用自定义控件来实现,方法也比较简单,这里就不讲解了,有兴趣的话,可以自行搜索
下面我将为大家讲解Fragment的 一些优化
解决Fragment多次实例化的几种方案
目前本人了解到的解决方案 ,无非是利用一下两种思想
第一种解决方法
在onCreateView中避免多次实例化View,可通过判断View是否为空,来实现相应的 逻辑操作,核心代码如下
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable
Bundle savedInstanceState) {
LUtils.i(getClass().getSimpleName()+">>>>>>>>>>> onCreateView");
if(mView==null){
mContext = getContext();
mView=View.inflate(mContext,getLayoutId(),null);
initView(mView);
LUtils.i(getClass().getSimpleName()+">>>>>>>>>>> initView");
}else{
// 缓存的rootView需要判断是否已经被加过parent,如果有parent需要从parent删除,
// 要不然会发生这个rootview已经有parent的错误。
ViewGroup parent =(ViewGroup) mView.getParent();
if(parent!=null){
parent.removeView(mView);
}
LUtils.i(getClass().getSimpleName()+">>>>>>>>>>> removeView");
}
return mView;
}
第二种解决方案
在项目中需要进行Fragment的切换,用hide()和show()方法结合起来来替代replace()方法来实现Fragment的切换:
private void showFragment(Fragment from, Fragment to) {
FragmentManager supportFragmentManager = getSupportFragmentManager();
FragmentTransaction transaction = supportFragmentManager.beginTransaction();
if (!to.isAdded()) { // 先判断是否被add过
transaction.hide(from).add(R.id.fl, to).commit(); // 隐藏当前的fragment,add下一个到Activity中
} else {
transaction.hide(from).show(to).commit(); // 隐藏当前的fragment,显示下一个
}
}
/**
* 这个方法用老替换fragment
* xujun
* 2016/5/3 17:28.
*/
private void replaceFragment(Fragment fragmeny) {
FragmentManager supportFragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = supportFragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fl, fragmeny).commit();
}
关于 避免Fragment的多次实例化的分析与优化到此为止,下面我们一起来看一下 则那样实现Fragemnt 的 懒加载
Fragemnt的懒加载(网上很多人称之为Fragemnt的最优加载)
关于懒加载的这部分,代码是参考这篇 博客的 ViewPager+Fragment LazyLoad最优解
我们知道 ViewPager通常 会有预加载机制,默认情况下会先加载左右一页的数据,有时候我们想等待页面可见的时候在去加载网络 数据 ,解决方案如下
下面 先贴出代码
public abstract class BasePageFragment extends Fragment {
protected View mView;
/**
* 表示View是否被初始化
*/
protected boolean isViewInitiated;
/**
* 表示对用户是否可见
*/
protected boolean isVisibleToUser;
/**
* 表示数据是否初始化
*/
protected boolean isDataInitiated;
private Context mContext;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LUtils.i(getClass().getSimpleName() + ">>>>>>>>>>> onCreate");
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable
Bundle savedInstanceState) {
LUtils.i(getClass().getSimpleName() + ">>>>>>>>>>> onCreateView");
if (mView == null) {
mContext = getContext();
mView = View.inflate(mContext, getLayoutId(), null);
initView(mView);
LUtils.i(getClass().getSimpleName() + ">>>>>>>>>>> initView");
} else {
// 缓存的rootView需要判断是否已经被加过parent,如果有parent需要从parent删除,
// 要不然会发生这个rootview已经有parent的错误。
ViewGroup parent = (ViewGroup) mView.getParent();
if (parent != null) {
parent.removeView(mView);
}
LUtils.i(getClass().getSimpleName() + ">>>>>>>>>>> removeView");
}
return mView;
}
protected abstract void initView(View view);
protected abstract int getLayoutId();
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
LUtils.i(getClass().getSimpleName() + ">>>>>>>>>>> onActivityCreated");
isViewInitiated = true;
initData();
prepareFetchData();
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
this.isVisibleToUser = isVisibleToUser;
prepareFetchData();
}
public abstract void fetchData();
public boolean prepareFetchData() {
return prepareFetchData(false);
}
/***
*
* @param forceUpdate 表示是否在界面可见的时候是否强制刷新数据
* @return
*/
public boolean prepareFetchData(boolean forceUpdate) {
if (isVisibleToUser && isViewInitiated && (!isDataInitiated || forceUpdate)) {
// 界面可见的时候再去加载数据
fetchData();
isDataInitiated = true;
return true;
}
return false;
}
@Override
public void onDestroyView() {
super.onDestroyView();
LUtils.i(getClass().getSimpleName() + ">>>>>>>>>>> onDestroyView");
}
protected void initData() {
}
}
思路解析:
其实核心思想主要在setUserVisibleHint(boolean isVisibleToUser)方法中,这个方法在Fragemnt界面切换到可见或者 不可见的时候调用,isVisibleToUser表示当前界面可见 或者不可见
setUserVisibleHint(boolean isVisibleToUser)
Set a hint to the system about whether this fragment's UI is currently visible to the user.
于是我们在prepareFetchData进行判断只有当界面可见的情况下才会尝试判断是否调用fetchData() 方法,于是我们就可以实现等到界面可见的 时候才加载网络数据,将我们加载网络数据的 逻辑的实现放在fetchData()
里面即可
/***
*
* @param forceUpdate 表示是否在界面可见的时候是否强制刷新数据
* @return
*/
public boolean prepareFetchData(boolean forceUpdate) {
if (isVisibleToUser && isViewInitiated && (!isDataInitiated || forceUpdate)) {
// 界面可见的时候再去加载数据
fetchData();
isDataInitiated = true;
return true;
}
return false;
}
题外话
转眼间校招季已经过去了一个多月了,虽然没能进入到bat,网易之类的公司,不过也找到了相对比较喜欢的公司,接下来的日子就专心做毕业设计了,计划在十一月的时候出省旅游一次,明年春节回来的时候再去公司实习,悄悄地告诉你一件事哦,本人 还没有 出过广东省 ,哈哈,是不是很low呢?
转载请注明博客地址:http://blog.csdn.net/gdutxiaoxu/article/details/52826810