麼是ViewPager?
關於ViewPager的介紹和使用,在之前我寫過一篇相關的文章《安卓開發復習筆記——ViewPager組件(仿微信引導界面)》,不清楚的朋友可以看看,這裡就不再重復。
什麼是Fragment?
Fragment是Android3.0後新增的概念,Fragment名為碎片,不過卻和Activity十分相似,具有自己的生命周期,它是用來描述一些行為或一部分用戶界面在一個Activity中,我們可以合並多個Fragment在一個單獨的activity中建立多個UI面板,或者重用Fragment在多個activity中。
關於Fragment的生命周期,由於Fragment需要依賴Activity,也就是說當一個Activity的生命周期結束之後,那麼Fragment的生命周期也自然結束。如果把一個Activiy比作一座大宅子的話,那麼Fragment就可以比作大宅子裡的房間,大宅子裡的房間其中一間倒塌了,並不會引起整個大宅子的倒塌,但如果大宅子倒塌了,那麼大宅裡的房間也就都倒塌了。
下面來看下Fragment的生命周期: Activity和Fragment生命周期對比(相似):
為了更好的理解Fragment,我找了下面的一張圖:
看左邊這張圖,它是我們傳統的手機界面,假設它現在呈現的是一個新聞列表頁,那麼當我們點擊列表項中,我們將會跳轉到新聞詳細頁中,上面是標題,下面是正文,這裡是2個Activity。
再看看右邊的圖,左邊是新聞列表頁,右邊是新聞詳細頁,我們可以動態的點擊左邊的列表項,使得右邊的新聞詳細頁動態變化,這裡只有1個Activity裡面嵌套了2個Fragment,左邊一個,右邊一個。
好了,做了簡單的介紹後,先來看看今天我們要實現的效果圖:(高仿微信主界面)
這裡我畫了張界面分析圖,畫圖永遠的痛,湊合著看哈
這裡的XML布局文件,我把每一部分都分開寫了:
top1.xml
復制代碼
1 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
2 xmlns:tools="http://schemas.android.com/tools"
3 android:layout_width="match_parent"
4 android:layout_height="50dp"
5 android:background="@drawable/bg"
6 android:paddingLeft="12dp"
7 android:paddingRight="12dp" >
8
9 <LinearLayout
10 android:layout_width="wrap_content"
11 android:layout_height="wrap_content"
12 android:layout_centerVertical="true"
13 android:gravity="center"
14 android:orientation="horizontal" >
15
16 <ImageView
17 android:layout_width="30dp"
18 android:layout_height="30dp"
19 android:src="@drawable/weixin" />
20
21 <TextView
22 android:layout_width="wrap_content"
23 android:layout_height="wrap_content"
24 android:layout_marginLeft="12dp"
25 android:text="微信"
26 android:textColor="@android:color/white"
27 android:textSize="18dp" />
28 </LinearLayout>
29
30 <LinearLayout
31 android:layout_width="wrap_content"
32 android:layout_height="wrap_content"
33 android:layout_alignParentRight="true"
34 android:layout_centerVertical="true"
35 android:gravity="center"
36 android:orientation="horizontal" >
37
38 <ImageView
39 android:layout_width="30dp"
40 android:layout_height="30dp"
41 android:src="@drawable/search" />
42
43 <ImageView
44 android:layout_width="30dp"
45 android:layout_height="30dp"
46 android:src="@drawable/add" />
47
48 <ImageView
49 android:layout_width="30dp"
50 android:layout_height="30dp"
51 android:src="@drawable/more" />
52 </LinearLayout>
53
54 </RelativeLayout>
復制代碼
top2.xml
復制代碼
1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
2 android:layout_width="match_parent"
3 android:layout_height="40dp"
4 android:orientation="vertical" >
5
6 <LinearLayout
7 android:layout_width="match_parent"
8 android:layout_height="37dp"
9 android:gravity="center_vertical"
10 android:background="#cccccc"
11 >
12
13 <LinearLayout
14 android:layout_width="wrap_content"
15 android:layout_height="wrap_content"
16 android:layout_weight="1"
17 android:gravity="center" >
18
19 <TextView
20 android:id="@+id/tv1"
21 android:layout_width="wrap_content"
22 android:layout_height="wrap_content"
23 android:text="聊天"
24 android:textColor="#339900"/>
25 </LinearLayout>
26
27 <LinearLayout
28 android:layout_width="wrap_content"
29 android:layout_height="wrap_content"
30 android:layout_weight="1"
31 android:gravity="center" >
32
33 <TextView
34 android:id="@+id/tv2"
35 android:layout_width="wrap_content"
36 android:layout_height="wrap_content"
37 android:text="發現"
38 android:textColor="@android:color/black"/>
39 </LinearLayout>
40
41 <LinearLayout
42 android:layout_width="wrap_content"
43 android:layout_height="wrap_content"
44 android:layout_weight="1"
45 android:gravity="center" >
46
47 <TextView
48 android:id="@+id/tv3"
49 android:layout_width="wrap_content"
50 android:layout_height="wrap_content"
51 android:text="通訊錄"
52 android:textColor="@android:color/black"/>
53 </LinearLayout>
54 </LinearLayout>
55
56 <LinearLayout
57 android:layout_width="match_parent"
58 android:layout_height="3dp" >
59
60 <ImageView
61 android:id="@+id/tabline"
62 android:layout_width="100dp"
63 android:layout_height="match_parent"
64 android:background="@drawable/tabline" />
65 </LinearLayout>
66
67 </LinearLayout>
復制代碼
mywx.xml(用include包含前2個布局文件,並設置垂直排列)
復制代碼
1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
2 xmlns:tools="http://schemas.android.com/tools"
3 android:layout_width="match_parent"
4 android:layout_height="match_parent"
5 android:orientation="vertical"
6 tools:context="com.example.weixin_test.MyWxTest" >
7
8 <include layout="@layout/top1" />
9
10 <include layout="@layout/top2" />
11
12
13 <android.support.v4.view.ViewPager
14 android:id="@+id/viewpager"
15 android:layout_width="match_parent"
16 android:layout_height="wrap_content"
17 android:layout_weight="1"
18 >
19
20
21 </android.support.v4.view.ViewPager>
22 </LinearLayout>
復制代碼
Fragment1.xml(由於Flagment的布局文件只是簡單采用字符標示,布局都一樣,這裡只給出第一個Fragment布局文件)
復制代碼
1 <?xml version="1.0" encoding="utf-8"?>
2 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 android:layout_width="match_parent"
4 android:layout_height="match_parent"
5 >
6
7 <TextView
8 android:layout_width="wrap_content"
9 android:layout_height="wrap_content"
10 android:text="我是第一個界面"
11 android:textSize="30dp"
12 android:layout_centerInParent="true"
13
14 />
15
16
17 </RelativeLayout>
復制代碼
接下來是JAVA代碼了,注釋很全(其實用法還是之前的ViewPager,只不過之前的ViewPager的數據源裡存放的是view對象,而這裡是Fragment)
復制代碼
1 package com.example.weixin_test;
2
3 import java.util.ArrayList;
4 import java.util.List;
5
6 import android.graphics.Color;
7 import android.os.Bundle;
8 import android.support.v4.app.Fragment;
9 import android.support.v4.app.FragmentActivity;
10 import android.support.v4.app.FragmentPagerAdapter;
11 import android.support.v4.view.ViewPager;
12 import android.support.v4.view.ViewPager.OnPageChangeListener;
13 import android.util.DisplayMetrics;
14 import android.util.Log;
15 import android.view.Display;
16 import android.view.ViewGroup.LayoutParams;
17 import android.view.Window;
18 import android.widget.ImageView;
19 import android.widget.LinearLayout;
20 import android.widget.TextView;
21
22 public class MyWxTest extends FragmentActivity {
23
24 private ViewPager viewPager;// 聲明一個viewpager對象
25 private TextView tv1;
26 private TextView tv2;
27 private TextView tv3;
28 private ImageView tabline;
29 private List<Fragment> list;// 聲明一個list集合存放Fragment(數據源)
30
31 private int tabLineLength;// 1/3屏幕寬
32 private int currentPage = 0;// 初始化當前頁為0(第一頁)
33
34 @Override
35 protected void onCreate(Bundle savedInstanceState) {
36 super.onCreate(savedInstanceState);
37 requestWindowFeature(Window.FEATURE_NO_TITLE);
38 setContentView(R.layout.mywx);
39 // 初始化滑動條1/3
40 initTabLine();
41
42 // 初始化界面
43 initView();
44 }
45
46 private void initTabLine() {
47 // 獲取顯示屏信息
48 Display display = getWindow().getWindowManager().getDefaultDisplay();
49 // 得到顯示屏寬度
50 DisplayMetrics metrics = new DisplayMetrics();
51 display.getMetrics(metrics);
52 // 1/3屏幕寬度
53 tabLineLength = metrics.widthPixels / 3;
54 // 獲取控件實例
55 tabline = (ImageView) findViewById(R.id.tabline);
56 // 控件參數
57 LayoutParams lp = tabline.getLayoutParams();
58 lp.width = tabLineLength;
59 tabline.setLayoutParams(lp);
60 }
61
62 private void initView() {
63 // 實例化對象
64 viewPager = (ViewPager) findViewById(R.id.viewpager);
65 tv1 = (TextView) findViewById(R.id.tv1);
66 tv2 = (TextView) findViewById(R.id.tv2);
67 tv3 = (TextView) findViewById(R.id.tv3);
68 list = new ArrayList<Fragment>();
69
70 // 設置數據源
71 Fragment1 fragment1 = new Fragment1();
72 Fragment2 fragment2 = new Fragment2();
73 Fragment3 fragment3 = new Fragment3();
74
75 list.add(fragment1);
76 list.add(fragment2);
77 list.add(fragment3);
78
79 // 設置適配器
80 FragmentPagerAdapter adapter = new FragmentPagerAdapter(
81 getSupportFragmentManager()) {
82
83 @Override
84 public int getCount() {
85 return list.size();
86 }
87
88 @Override
89 public Fragment getItem(int arg0) {
90 return list.get(arg0);
91 }
92 };
93
94 // 綁定適配器
95 viewPager.setAdapter(adapter);
96
97 // 設置滑動監聽
98 viewPager.setOnPageChangeListener(new OnPageChangeListener() {
99
100 @Override
101 public void onPageSelected(int position) {
102 // 當頁面被選擇時,先講3個textview的字體顏色初始化成黑
103 tv1.setTextColor(Color.BLACK);
104 tv2.setTextColor(Color.BLACK);
105 tv3.setTextColor(Color.BLACK);
106
107 // 再改變當前選擇頁(position)對應的textview顏色
108 switch (position) {
109 case 0:
110 tv1.setTextColor(Color.rgb(51, 153, 0));
111 break;
112 case 1:
113 tv2.setTextColor(Color.rgb(51, 153, 0));
114 break;
115 case 2:
116 tv3.setTextColor(Color.rgb(51, 153, 0));
117 break;
118 }
119
120 currentPage = position;
121
122 }
123
124 @Override
125 public void onPageScrolled(int arg0, float arg1, int arg2) {
126 Log.i("tuzi", arg0 + "," + arg1 + "," + arg2);
127
128 // 取得該控件的實例
129 LinearLayout.LayoutParams ll = (android.widget.LinearLayout.LayoutParams) tabline
130 .getLayoutParams();
131
132 if (currentPage == 0 && arg0 == 0) { // 0->1移動(第一頁到第二頁)
133 ll.leftMargin = (int) (currentPage * tabLineLength + arg1
134 * tabLineLength);
135 } else if (currentPage == 1 && arg0 == 1) { // 1->2移動(第二頁到第三頁)
136 ll.leftMargin = (int) (currentPage * tabLineLength + arg1
137 * tabLineLength);
138 } else if (currentPage == 1 && arg0 == 0) { // 1->0移動(第二頁到第一頁)
139 ll.leftMargin = (int) (currentPage * tabLineLength - ((1 - arg1) * tabLineLength));
140 } else if (currentPage == 2 && arg0 == 1) { // 2->1移動(第三頁到第二頁)
141 ll.leftMargin = (int) (currentPage * tabLineLength - (1 - arg1)
142 * tabLineLength);
143 }
144
145 tabline.setLayoutParams(ll);
146
147 }
148
149 @Override
150 public void onPageScrollStateChanged(int arg0) {
151 // TODO Auto-generated method stub
152
153 }
154 });
155
156 }
157
158 }
復制代碼
對這個類做下說明:
1、這裡的滑動屏幕下劃線動態跟隨的效果,其實實現方法有2種,原理是一樣的
(1)可以使用ViewPager的兩個子類ViewFlipper和ViewSwitche,這種方法比較簡單,直接用就行。
(2)用原生代碼實現,也就是動態的去控制下劃線的左外邊距。
這裡我采用的是第2種方法,我覺得授人予魚還不如授人予漁,其實也並不復雜,細節去理下細節就懂了。
這裡需要注意一個地方,我們在給ViewPager設置監聽器時,這邊會復寫一個onPageScrolled方法,裡面有3個參數,我用Log打印出它們在頁面滑動時的數據變化
這是頁面一向頁面二滑動時候的數據記錄:
我們可以發現第一個參數值直接從0->1,第二個參數值從0.0依次增加到0.9xx無限靠近1,然後頁面到達第二頁它又恢復成了0,第三個參數從1開始累積到300+(這個我們不去關注)
這是頁面二向頁面三滑動時候的數據記錄:
我們可以發現第一個參數值直接從1->2,第二個參數值從0.0依次增加到0.9xx無限靠近1,然後頁面到達第二頁它又恢復成了0,第三個參數從1開始累積到300+(這個我們不去關注)
因此我們可以發現一個規律:
當ViewPager頁面值為0(第一頁)且當參數一為0時,頁面的狀態時從 第一頁到第二頁
當ViewPager頁面值為1(第二頁)且當參數一為1時,頁面的狀態時從 第一頁到第二頁
以此類推,大家可以自己打印出來看看,對這些數據比較有感覺,由於文章篇幅問題,這裡就不再貼圖了。
我們可以利用第二個參數從0.0推薦遞增到1,這個數據來控制左外邊距(在第一頁時左外邊距為0,第二頁時左外邊距為1/3屏幕寬,第三頁時左外邊距為2/3屏幕寬)
由此推導出的公式為:
向左滑時:當前頁數*屏幕1/3寬+onPageScrolled方法第二個參數*屏幕1/3寬
向右滑時:當前頁數*屏幕1/3寬-(1-onPageScrolled方法第二個參數)*屏幕1/3寬
2、由於這裡使用到了Fragment,這裡就不再和以往一樣繼承Activity,這裡需要繼承Activity的子類FragmentActivity。
由於3個Fragment的代碼幾乎一致,所以這裡只給出Fragment1.java
復制代碼
1 package com.example.weixin_test;
2
3 import android.os.Bundle;
4 import android.support.annotation.Nullable;
5 import android.support.v4.app.Fragment;
6 import android.view.LayoutInflater;
7 import android.view.View;
8 import android.view.ViewGroup;
9
10 public class Fragment1 extends Fragment {
11 @Override
12 public View onCreateView(LayoutInflater inflater,
13 @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
14 return inflater.inflate(R.layout.fragment1, container, false);
15 }
16
17 }
復制代碼
來講一下關於這個類的說明:
1、Fragment一般是作為Activity界面的一部分,它把Layout對象嵌入到了Activity之中,若要對一個Fragment提供Layout對象必須去調用一個onCreateView()方法,它的返回值是一個View對象,這個方法為我們提供了一個LayoutInflater便於我們把XML布局文件轉換成View對象。
2、onCreateView()方法中:
container參數是用來存放Fragment的layout。
saveInstanceState參數是一個Bundle,跟Activity的onCreate()中Bundle差不多,用於狀態恢復。
3、inflate()方法中有三個參數:
1:layout的資源id。
2:存放fragment的layout的ViewGroup。
3:這個布爾值是代表是否在創建Fragment的layout期間,把layout附加到container上,由於系統已經把layout對象存放在了ViewGroup中,所以這裡為false。