一路克、概述
在 Android 開(kāi)發(fā)中,經(jīng)常需要使用頂部或者底部的導(dǎo)航來(lái)切換當(dāng)前顯示的 Fragment养交。
在很多應(yīng)用中還添加了滑動(dòng)切換的效果衷戈,大體效果如下:
這類程序分為兩個(gè)部分。
下方使用 ViewPager 實(shí)現(xiàn)多頁(yè)滑動(dòng)顯示层坠。滑動(dòng)時(shí)刁笙,ViewPager 顯示不同的 Fragment破花,我們可以為 ViewPager 設(shè)置適配器來(lái)實(shí)現(xiàn)這樣的效果谦趣。
上方的四個(gè) TextView 的顯示需要我們自己實(shí)現(xiàn),主要是在 ViewPager 切換的時(shí)候進(jìn)行文字顏色的設(shè)置以及下方橫線的滑動(dòng)座每。
程序源碼:PagerSlide
二前鹅、Fragment
ViewPager 本身是一個(gè)可以滑動(dòng)的對(duì)象,我們可以在其中添加滑動(dòng)的廣告峭梳,或者是這里說(shuō)的 Fragment 的切換舰绘。
如果只是添加圖片之類的控件,我們只需要設(shè)置相應(yīng)的布局文件即可葱椭,但是添加 Fragment 卻不是這么簡(jiǎn)單的捂寿。下面我們從 Fragment 生命周期開(kāi)始講起。
1. Fragment 生命周期
Fragment 的生命周期很復(fù)雜孵运,我們只看重點(diǎn)秦陋,F(xiàn)ragment 在 onCreateView() 中加載視圖。經(jīng)過(guò) onActivityCreate() --> onStart() --> onResume() 后才真正顯示治笨。
而在 Fragment 顯示前驳概,還有一個(gè) onActivityCreate() 函數(shù),我們可以在這里加載 Fragment 所需要的數(shù)據(jù)(這個(gè)例子沒(méi)有數(shù)據(jù)旷赖,但在真正的項(xiàng)目里顺又,這里一般加載聯(lián)網(wǎng)數(shù)據(jù))。
2. BaseFragment
我們創(chuàng)建一個(gè)繼承自 Fragment(support.v4 包) 的抽象類 BaseFragment等孵,在里面實(shí)現(xiàn)一些公共的方法稚照。我們所有的自定義 Fragment 都將繼承自 BaseFragment。
BaseFragment 的子類必須都重寫(xiě) initView() 方法(因?yàn)槊總€(gè) Fragment 都需要加載布局)流济,這個(gè)方法返回當(dāng)前 Fragment 的 View 對(duì)象锐锣。
而在 onActivityCreated() 方法中我們通過(guò) initData() 加載數(shù)據(jù),如果子類需要加載數(shù)據(jù)并重寫(xiě)了此方法绳瘟,那么根據(jù)上面講的生命周期雕憔,數(shù)據(jù)就會(huì)在 Fragment 顯示前加載完畢。
public abstract class BaseFragment extends Fragment {
// 上下文對(duì)象
protected Context mContext;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext = getActivity();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return initView();
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
initData();
}
// 繼承此類的子類必須重寫(xiě)此方法加載布局
public abstract View initView();
// 加載數(shù)據(jù)的方法
public void initData() { }
}
3. 子 Fragment
有了 BaseFragment糖声,我們就可以自定義需要顯示的 Fragment 了斤彼。Fragment 的布局文件隨你樂(lè)意,這里我只加了一張圖片蘸泻。
我們?cè)?initView() 中加載并返回了 View 視圖對(duì)象琉苇,在 initData() 中加載數(shù)據(jù)。這兩個(gè)方法里都有 Log 日志打印悦施,這個(gè)待會(huì)有用并扇。
public class Fragment1 extends BaseFragment {
@Override
public View initView() {
Log.e("TAG", "Fragment1 --> initView");
View view = View.inflate(mContext, R.layout.fragment1, null);
return view;
}
@Override
public void initData() {
super.initData();
// ......加載數(shù)據(jù)
Log.e("TAG", "Fragment1 --> initData");
}
}
之后再定義三個(gè)相似的 Fragment 即可。
三抡诞、布局文件
定義四個(gè)橫向的 Textview 用于頂部導(dǎo)航穷蛹。
<?xml version="1.0" encoding="utf-8"?>
<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="vertical"
tools:context="com.learn.lister.pagerslide.activity.MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="10dp"
android:gravity="center">
<TextView
android:id="@+id/page_0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="首頁(yè)"
android:textSize="16sp"
android:textColor="@android:color/black"/>
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="10dp"
android:gravity="center">
<TextView
android:id="@+id/page_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="朋友"
android:textSize="16sp"
android:textColor="@android:color/black"/>
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="10dp"
android:gravity="center">
<TextView
android:id="@+id/page_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="動(dòng)態(tài)"
android:textSize="16sp"
android:textColor="@android:color/black"/>
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="10dp"
android:gravity="center">
<TextView
android:id="@+id/page_3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="附近"
android:textSize="16sp"
android:textColor="@android:color/black"/>
</LinearLayout>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="2dp"
android:background="@android:color/darker_gray"/>
<ImageView
android:id="@+id/main_tab_line"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/slider"/>
<android.support.v4.view.ViewPager
android:id="@+id/main_pager"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v4.view.ViewPager>
</LinearLayout>
四土陪、主要代碼
1. 適配器
為了支持在 ViewPager 滑動(dòng)時(shí)向其中添加不同的 Fragment,我們需要為 ViewPager 設(shè)置一個(gè)適配器肴熏。我們可以自定義一個(gè)繼承于 FragmentPagerAdapter 的適配器鬼雀。
官方文檔對(duì) FragmentPagerAdapter 的解釋大致如下:
FragmentPagerAdapter 派生自 PagerAdapter,它是用來(lái)呈現(xiàn)Fragment頁(yè)面的蛙吏,這些Fragment頁(yè)面會(huì)一直保存在fragment manager中源哩,以便用戶可以隨時(shí)取用。
這個(gè)適配器適用于有限個(gè)靜態(tài)fragment頁(yè)面的管理鸦做。盡管不可見(jiàn)的視圖有時(shí)會(huì)被銷毀励烦,但用戶所有訪問(wèn)過(guò)的fragment都會(huì)被保存在內(nèi)存中。
而繼承自 FragmentPagerAdapter 的適配器也只需要重寫(xiě) getCount() 和 getItem(int position) 兩個(gè)方法馁龟。
/**
* Fragment 滑動(dòng)適配器
* BaseFragment 為自定義的 Fragment 基類崩侠。
*/
public class PagerSlideAdapter extends FragmentPagerAdapter {
private List<BaseFragment> mFragmentList;
public PagerSlideAdapter(FragmentManager fm, List<BaseFragment> fragmentList) {
super(fm);
this.mFragmentList = fragmentList;
}
@Override
public Fragment getItem(int position) {
return mFragmentList.get(position);
}
@Override
public int getCount() {
return mFragmentList.size();
}
}
從代碼中我們可以看出,在構(gòu)造函數(shù)中需要傳入一個(gè) Fragment 的合集并初始化坷檩,這些就是 ViewPager 中滑動(dòng)的對(duì)象却音。
2. MainActivity
ViewPager 的滑動(dòng)是設(shè)置適配器的效果,而滑動(dòng)頁(yè)面時(shí)文字的變化以及橫條的移動(dòng)就需要我們自己動(dòng)手了矢炼。
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
@BindView(R.id.page_0) TextView text0;
@BindView(R.id.page_1) TextView text1;
@BindView(R.id.page_2) TextView text2;
@BindView(R.id.page_3) TextView text3;
@BindView(R.id.main_tab_line) ImageView tab_line;
@BindView(R.id.main_pager) ViewPager mViewPager;
private int screenWidth;
private List<BaseFragment> mFragmentList = new ArrayList<>();
private PagerSlideAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
initData(); // 初始化數(shù)據(jù)
initWidth(); // 初始化滑動(dòng)橫條的寬度
setListener(); // 設(shè)置監(jiān)聽(tīng)器
}
private void initData() {
// 將我們自定義 Fragment 的對(duì)象添加到 List<BaseFragment> 中系瓢。
mFragmentList.add(new Fragment1());
mFragmentList.add(new Fragment2());
mFragmentList.add(new Fragment3());
mFragmentList.add(new Fragment4());
// 新建適配器
adapter = new PagerSlideAdapter(getSupportFragmentManager(), mFragmentList);
// 為 ViewPager 設(shè)置適配器
mViewPager.setAdapter(adapter);
// 打開(kāi)應(yīng)用時(shí) ViewPager 顯示第一個(gè) Fragment
mViewPager.setCurrentItem(0);
text0.setTextColor(Color.BLUE);
}
private void setListener() {
mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
/**
* This method will be invoked when the current page is scrolled, either as part
* of a programmatically initiated smooth scroll or a user initiated touch scroll.
*
* @param position Position index of the first page currently being displayed.
* Page position+1 will be visible if positionOffset is nonzero.
* @param positionOffset Value from [0, 1) indicating the offset from the page at position.
* @param positionOffsetPixels Value in pixels indicating the offset from position.
* 這個(gè)參數(shù)的使用是為了在滑動(dòng)頁(yè)面時(shí)有文字下方橫條的滑動(dòng)效果
*/
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) tab_line.getLayoutParams();
lp.leftMargin = screenWidth/4*position + positionOffsetPixels/4;
tab_line.setLayoutParams(lp);
}
@Override
public void onPageSelected(int position) {
// 在每次切換頁(yè)面時(shí)重置 TextView 的顏色
resetTextView();
switch (position) {
case 0:
text0.setTextColor(Color.BLUE);
break;
case 1:
text1.setTextColor(Color.BLUE);
break;
case 2:
text2.setTextColor(Color.BLUE);
break;
case 3:
text3.setTextColor(Color.BLUE);
break;
}
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
text0.setOnClickListener(this);
text1.setOnClickListener(this);
text2.setOnClickListener(this);
text3.setOnClickListener(this);
}
private void resetTextView() {
text0.setTextColor(Color.BLACK);
text1.setTextColor(Color.BLACK);
text2.setTextColor(Color.BLACK);
text3.setTextColor(Color.BLACK);
}
// 初始化滑動(dòng)橫條的寬度
private void initWidth() {
DisplayMetrics dpMetrics = new DisplayMetrics();
getWindow().getWindowManager().getDefaultDisplay().getMetrics(dpMetrics);
screenWidth = dpMetrics.widthPixels;
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) tab_line.getLayoutParams();
lp.width = screenWidth / 4;
tab_line.setLayoutParams(lp);
}
// 設(shè)置文字的點(diǎn)擊事件,點(diǎn)擊某個(gè) TextView 就跳到相應(yīng)頁(yè)面
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.page_0:
mViewPager.setCurrentItem(0);
break;
case R.id.page_1:
mViewPager.setCurrentItem(1);
break;
case R.id.page_2:
mViewPager.setCurrentItem(2);
break;
case R.id.page_3:
mViewPager.setCurrentItem(3);
break;
}
}
}
五句灌、Fragment 的緩存
到這里我們的程序已經(jīng)可以運(yùn)行了夷陋,但還記得我們之前在自定義 Fragment 類中的 Log 日志嗎?運(yùn)行程序胰锌,讓我們看一下這個(gè)日志骗绕。
程序剛運(yùn)行時(shí)日志:
E/TAG: Fragment1 --> initView
E/TAG: Fragment1 --> initData
E/TAG: Fragment2 --> initView
E/TAG: Fragment2 --> initData
程序剛打開(kāi)時(shí)不是只顯示一個(gè) Fragment 嗎?為什么會(huì)加載兩個(gè) Fragment 的資源资昧?這時(shí)滑動(dòng)到第二個(gè) Fragment酬土,你會(huì)發(fā)現(xiàn)日志是這樣的:
E/TAG: Fragment3 --> initView
E/TAG: Fragment3 --> initData
看起來(lái)適配器總是會(huì)預(yù)先加載一個(gè)頁(yè)面,但是當(dāng)你滑動(dòng)到最后一個(gè)頁(yè)面格带,再往前滑動(dòng)時(shí)撤缴,日志是這樣的:
E/TAG: Fragment2 --> initView
E/TAG: Fragment2 --> initData
Fragment2 之前不是加載過(guò)了嗎?怎么又來(lái)叽唱?
其實(shí)是這樣屈呕,適配器為你保存在內(nèi)存中的 Fragment 時(shí)當(dāng)前所顯示的 Fragmen以及當(dāng)前 Fragment 的前一個(gè)和后一個(gè)。在內(nèi)存中最多只會(huì)緩存三個(gè) Fragment棺亭。(剛打開(kāi)時(shí)只緩存了兩個(gè))
六虎眨、總結(jié)
這里講到了滑動(dòng) ViewPager 顯示不同 Fragment,但是這里的 Fragment 都是靜態(tài)的,如果要處理大量的頁(yè)面切換专甩,F(xiàn)ragmentStatePagerAdapter 會(huì)更優(yōu)秀钟鸵,有興趣的話就去學(xué)習(xí)一下吧。