感觉 Android 到处都是坑,每个地方都要把人折腾半天。
今天来简单说说 Android之ActionBar、Tabs、Fragment、ViewPager 实现标签页切换并缓存页面
关于他们的介绍就不多说了,网上到处都是,只说关键的部分:
我在开发的时候遇到几个疑难问题,花费大量时间处理,总结如下:
1. 关于 Fragment 内部逻辑处理该写在哪个事件回调部分?
2. ViewPager 页面切换动画卡顿,让我头疼了很久。
3. ViewPager 中如何保存 Fragment 当前视图的状态,让 Tabs 页面切换后不会重新加载,这地方很坑爹
4. ActionBar 中的 tab 很多时如何滚动显示
解答:
一、Fragment 的事件回调:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
|
package com.ai9475.meitian.ui.fragment;
import android.app.Activity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListView;
import com.ai9475.meitian.R;
import com.ai9475.meitian.view.DiaryList;
import com.ai9475.util.ZLog;
/** *
* Created by ZHOUZ on 14-1-21.
*/
public class DiaryListFragment extends BaseFragment
{ private static final String TAG = "DiaryListFragment" ;
@Override
public void onAttach(Activity activity)
{
ZLog.i(TAG, "onAttach" );
super .onAttach(activity);
}
@Override
public void onCreate(Bundle savedInstanceState)
{
ZLog.i(TAG, "onCreate" );
super .onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
ZLog.i(TAG, "onCreateView" );
return inflater.inflate(R.layout.fragment_diary_list, container, false );
}
@Override
public void onActivityCreated(Bundle savedInstanceState)
{
ZLog.i(TAG, "onActivityCreated" );
super .onActivityCreated(savedInstanceState);
ZLog.i(TAG, "DiaryList0" );
DiaryList diaryList = new DiaryList(
getActivity().getApplicationContext(),
(ListView) getView().findViewById(R.id.diaryListCt)
);
ZLog.i(TAG, "DiaryList load0" );
this .setRetainInstance( true );
}
@Override
public void onStart()
{
ZLog.i(TAG, "onStart" );
super .onStart();
}
@Override
public void onResume()
{
ZLog.i(TAG, "onResume" );
super .onResume();
}
@Override
public void onPause()
{
ZLog.i(TAG, "onPause" );
super .onPause();
}
@Override
public void onStop()
{
ZLog.i(TAG, "onStop" );
super .onStop();
}
@Override
public void onDestroyView()
{
ZLog.i(TAG, "onDestroyView" );
super .onDestroyView();
}
@Override
public void onDestroy()
{
ZLog.i(TAG, "onDestroy" );
super .onDestroy();
}
@Override
public void onDetach()
{
ZLog.i(TAG, "onDetach" );
super .onDetach();
}
} |
上面的类中的 on 事件就是Fragment主要处理的时间回调,注意复写父类方法时要回调执行父类同名方法,否则会出错
主要复写 onCreateView 方法,返回该 Fragment 所对应的视图对象,这里可以在返回视图对象前进行一些简单的配置,但千万不要写太耗时的处理阻塞UI主线程。
另外 onActivityCreated 方法,是当 activity 的 onCreate 事件结束时的回调,此时当前的Fragment对应的view已经并入到整个布局中,此时可以使用 getView() 方法获取视图对象。
其他几个事件没什么太多可说,有些我也还不是太清楚,还有些动画调用的事件。
二、切换页面卡顿问题
这个问题的产生主要可能是两方面,
1. 没有使用 ViewPager 的缓存,每次切换都重新加载。
2. 加载 Fragment 内部有耗时耗资源的逻辑处理。
这里主要说下第二种情况,我一开始没处理掉 缓存问题时有一个解决办法,
1
2
3
4
5
6
|
< android.support.v4.view.ViewPager
android:id = "@+id/tabsViewPager"
android:layout_width = "match_parent"
android:layout_height = "match_parent"
>
</ android.support.v4.view.ViewPager >
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
|
mViewPager = (ViewPager) findViewById(R.id.tabsViewPager); mViewPager.setOnPageChangeListener( new ViewPager.SimpleOnPageChangeListener() {
private static final String TAG = "ViewPager.SimpleOnPageChangeListener" ;
private ArrayList hasLoadedPages = new ArrayList<Integer>();
@Override
public void onPageSelected( int position) {
ZLog.i(TAG, "onPageSelected position:" + position);
getSupportActionBar().setSelectedNavigationItem(position);
}
@Override
public void onPageScrolled( int position, float positionOffset, int positionOffsetPixels)
{
ZLog.i(TAG, "onPageScrolled position: " + position + ", positionOffset:" + positionOffset + ", positionOffsetPixels:" + positionOffsetPixels);
}
@Override
public void onPageScrollStateChanged( int state) {
ZLog.i(TAG, "onPageScrollStateChanged" );
int position = mViewPager.getCurrentItem();
switch (state) {
// 正在拖动
case ViewPager.SCROLL_STATE_DRAGGING :
ZLog.i(TAG, "ViewPager.SCROLL_STATE_DRAGGING position:" + position);
break ;
// 拖动释放后正在沉降的过程
case ViewPager.SCROLL_STATE_SETTLING :
ZLog.i(TAG, "ViewPager.SCROLL_STATE_SETTLING position:" + position);
break ;
// 切换动画全部完成结束
case ViewPager.SCROLL_STATE_IDLE :
ZLog.i(TAG, "ViewPager.SCROLL_STATE_IDLE position:" + position);
// 已加载过则不再加载
if (hasLoadedPages.contains(position)) break ;
Fragment fragment = mPager.getFragments().get(position);
runCallback(position, fragment);
hasLoadedPages.add(position);
break ;
}
}
public void runCallback( int position, Fragment fragment) {
ZLog.i(TAG, "runCallback" );
DiaryList diaryList;
switch (position) {
case 0 :
ZLog.i(TAG, "DiaryList0" );
diaryList = new DiaryList(
getApplicationContext(),
(ListView) fragment.getView().findViewById(R.id.diaryListCt)
);
ZLog.i(TAG, "DiaryList load0" );
break ;
case 1 :
ZLog.i(TAG, "DiaryList1" );
diaryList = new DiaryList(
getApplicationContext(),
(ListView) fragment.getView().findViewById(R.id.diaryListCt)
);
ZLog.i(TAG, "DiaryList load1" );
break ;
case 2 :
ZLog.i(TAG, "DiaryList2" );
diaryList = new DiaryList(
getApplicationContext(),
(ListView) fragment.getView().findViewById(R.id.diaryListCt)
);
ZLog.i(TAG, "DiaryList load2" );
break ;
}
}
}
|
这里主要用到 public void onPageScrollStateChanged(int state) 页面滚动切换状态变化的事件监听
当滚动动画完全结束 case ViewPager.SCROLL_STATE_IDLE 时再执行 Fragment 的逻辑处理,这样动画就会流畅了。
但经过后来的测试发现有个很简单的解决方案,就是下面要说到的 ViewPager 的缓存功能,其实很简单。
三、缓存 Tabs 页面切换不重新加载数据
我在这地方折腾的最久,而且很多时候无从下手的感觉,网上搜索了很多文章都有说道保存 Fragment 数据和状态,但是没有整整提到如何来保存他的当前view状态,不知道如何保存,当然实际上我还是没搞懂如何仅仅缓存 Fragment 的状态,但对于 ViewPager 缓存 Tab 对应的 Fragment 还是找到了办法,之前花了很大功夫来自己实现,后来偶然发现他居然有个自带的方法
1
2
|
// 设置缓存多少个 Tab对应的 fragment mViewPager.setOffscreenPageLimit(6);
|
我测试时用了 6个 listView 加载图片列表数据,切换动画也没有任何卡顿现象,非常流畅,就这么简单一句就搞定了。
配置该项后,ViewPager在切换时将不会清理不可见的 Fragment,不会触发 Fragment 的任何事件,因此也就不会导致其重新加载。
四、ActionBar 中的 Tabs
这个其实不用操作,在tabs数量超过一屏后,例如我现在设置6个宽度超过了屏幕则会变成可以横向滚动的状态,而不需要自己实现,之前不知道在这方面查了很多资料都无果,只知道可以用 tabhost 可以实现,但在ActionBar 里面却又用不了,自己试了下方多个tab才发现原来会自动实现,无语。
还是贴上 MainActivity.class 完整代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
|
package com.ai9475.meitian.ui;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.ViewPager;
import android.support.v7.app.ActionBar;
import android.view.Menu;
import android.widget.ListView;
import com.ai9475.meitian.AppManager;
import com.ai9475.meitian.R;
import com.ai9475.meitian.ui.fragment.DiaryListFragment;
import com.ai9475.meitian.ui.fragment.Test2Fragment;
import com.ai9475.meitian.ui.fragment.Test3Fragment;
import com.ai9475.meitian.view.DiaryList;
import com.ai9475.util.ZLog;
import java.util.ArrayList;
public class MainActivity extends BaseActivity
{ private static final String TAG = "MainActivity" ;
private MyTabsPagerAdapter mPager;
private ViewPager mViewPager;
@Override
protected void onCreate(Bundle savedInstanceState)
{
ZLog.i(TAG, "start" );
// 执行父级初始化方法
super .onCreate(savedInstanceState);
//requestWindowFeature(Window.FEATURE_NO_TITLE);
ZLog.i(TAG, "setContentView" );
setContentView(R.layout.activity_main);
// 滑动页面视图配置
ZLog.i(TAG, "MyTabsPagerAdapter start" );
mPager = new MyTabsPagerAdapter(getSupportFragmentManager());
mPager.getFragments().add( new DiaryListFragment());
mPager.getFragments().add( new Test2Fragment());
mPager.getFragments().add( new Test3Fragment());
mPager.getFragments().add( new Test3Fragment());
mPager.getFragments().add( new Test3Fragment());
mPager.getFragments().add( new Test3Fragment());
// 滑动分页容器
mViewPager = (ViewPager) findViewById(R.id.tabsViewPager);
// 设置缓存多少个 fragment
mViewPager.setOffscreenPageLimit( 6 );
mViewPager.setAdapter(mPager);
// 页面滑动事件
mViewPager.setOnPageChangeListener(
new ViewPager.SimpleOnPageChangeListener() {
private static final String TAG = "ViewPager.SimpleOnPageChangeListener" ;
private ArrayList hasLoadedPages = new ArrayList<Integer>();
@Override
public void onPageSelected( int position) {
ZLog.i(TAG, "onPageSelected position:" + position);
getSupportActionBar().setSelectedNavigationItem(position);
}
@Override
public void onPageScrolled( int position, float positionOffset, int positionOffsetPixels)
{
ZLog.i(TAG, "onPageScrolled position: " + position + ", positionOffset:" + positionOffset + ", positionOffsetPixels:" + positionOffsetPixels);
}
@Override
public void onPageScrollStateChanged( int state) {
ZLog.i(TAG, "onPageScrollStateChanged" );
int position = mViewPager.getCurrentItem();
switch (state) {
// 正在拖动
case ViewPager.SCROLL_STATE_DRAGGING :
ZLog.i(TAG, "ViewPager.SCROLL_STATE_DRAGGING position:" + position);
break ;
// 拖动释放后正在沉降的过程
case ViewPager.SCROLL_STATE_SETTLING :
ZLog.i(TAG, "ViewPager.SCROLL_STATE_SETTLING position:" + position);
break ;
// 切换动画全部完成结束
case ViewPager.SCROLL_STATE_IDLE :
ZLog.i(TAG, "ViewPager.SCROLL_STATE_IDLE position:" + position);
/*if (hasLoadedPages.contains(position)) break;
Fragment fragment = mPager.getFragments().get(position);
runCallback(position, fragment);
hasLoadedPages.add(position);*/
break ;
}
}
public void runCallback( int position, Fragment fragment) {
ZLog.i(TAG, "runCallback" );
DiaryList diaryList;
switch (position) {
case 0 :
ZLog.i(TAG, "DiaryList0" );
diaryList = new DiaryList(
getApplicationContext(),
(ListView) fragment.getView().findViewById(R.id.diaryListCt)
);
ZLog.i(TAG, "DiaryList load0" );
break ;
case 1 :
ZLog.i(TAG, "DiaryList1" );
diaryList = new DiaryList(
getApplicationContext(),
(ListView) fragment.getView().findViewById(R.id.diaryListCt)
);
ZLog.i(TAG, "DiaryList load1" );
break ;
case 2 :
ZLog.i(TAG, "DiaryList2" );
diaryList = new DiaryList(
getApplicationContext(),
(ListView) fragment.getView().findViewById(R.id.diaryListCt)
);
ZLog.i(TAG, "DiaryList load2" );
break ;
}
}
}
);
ActionBar actionBar = getSupportActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
// Tab 页面切换
MyTabListener listener = new MyTabListener();
// 默认的首页 tab
ActionBar.Tab indexTab = actionBar.newTab()
.setText(getString(R.string.tab_index))
.setTabListener(listener);
actionBar.addTab(indexTab);
actionBar.addTab(actionBar.newTab()
.setText(getString(R.string.tab_hot))
.setTabListener(listener)
);
actionBar.addTab(actionBar.newTab()
.setText(getString(R.string.tab_tag))
.setTabListener(listener)
);
actionBar.addTab(actionBar.newTab()
.setText(getString(R.string.tab_tag))
.setTabListener(listener)
);
actionBar.addTab(actionBar.newTab()
.setText(getString(R.string.tab_tag))
.setTabListener(listener)
);
actionBar.addTab(actionBar.newTab()
.setText(getString(R.string.tab_tag))
.setTabListener(listener)
);
// 显示首页
indexTab.select();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true ;
}
private class MyTabListener implements ActionBar.TabListener
{
@Override
public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft)
{
int position = tab.getPosition();
ZLog.i(TAG, "tab selected: " + position);
// 数据通信
/*Bundle bundle = new Bundle();
Fragment fragment = mPager.getItem(tab.getPosition());
Toast.makeText(getApplicationContext(), "position:"+ tab.getPosition(), Toast.LENGTH_SHORT).show();
fragment.setArguments(bundle);
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.add(R.id.fragmentContainer, fragment);
fragmentTransaction.commit();*/
mViewPager.setCurrentItem(position);
}
@Override
public void onTabReselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
ZLog.i(TAG, "tab reselected: " + tab.getPosition());
}
@Override
public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft)
{
ZLog.i(TAG, "tab unselected: " + tab.getPosition());
}
};
public class MyTabsPagerAdapter extends FragmentPagerAdapter
{
private ArrayList<Fragment> mFragments = new ArrayList<Fragment>();
public MyTabsPagerAdapter(FragmentManager fm) {
super (fm);
}
public ArrayList<Fragment> getFragments() {
return this .mFragments;
}
@Override
public Fragment getItem( int i) {
return this .mFragments.get(i);
}
@Override
public int getCount() {
return this .mFragments.size();
}
}
} |
相关推荐
在Android 3.0(API级别11)及以上版本,`ActionBar`引入了`ActionBar.Tab`功能,用于实现标签页切换。在更早的版本或者对于不支持`ActionBar`的设备,可以通过`android.support.v7.app.ActionBar`(AppCompat库)来...
这些组件可以帮助你创建可滑动的标签页,与ActionBar相结合,可以模拟QQ顶部的聊天切换栏。在布局文件中添加`TabLayout`,并连接到一个`ViewPager`: ```xml <androidx.viewpager.widget.ViewPager android:id="@+...
在Android应用开发中,Fragment是实现界面动态更新和模块化设计的重要组件,尤其在构建复杂的UI布局时,如Tab切换页面。"Fragment切换 核心代码"这一主题着重讲解了如何在Android应用中有效地管理和切换Fragment,这...
本示例主要讲解如何使用`ActionBar`,特别是涉及`ActionBar.Tab`来实现多标签页切换的功能。下面将详细阐述`ActionBar`的基本概念、自定义菜单以及使用`Tab`进行导航的相关知识点。 1. **ActionBar的基本概念**: ...
通常,我们可以使用Android的TabHost、ActionBar(在API 11及以上版本)或ViewPager配合Fragment来实现Tab功能。下面将详细讨论这些实现方式及其相关知识点。 首先,让我们看看TabHost。TabHost是Android SDK提供的...
这个项目旨在帮助开发者理解如何在Android中实现多标签页切换,并且在每个标签页下可以进一步展示子页面,也就是所谓的"nested tabs"(嵌套标签)。 首先,我们来详细了解一下各个组件: 1. **ActionBar**:是...
至于博客中提到的`TabExample`,这可能是指使用`TabLayout`和`ViewPager`实现的标签页切换。`TabLayout`可以与`ViewPager`配合,提供在多个视图之间平滑切换的功能。在布局文件中,我们需要添加`TabLayout`和`...
2. 对于TabLayout,可以在布局文件中添加`<androidx.tabs.widget.TabLayout>`,并设置其适配器`TabLayout.setupWithViewPager(viewPager);` 3. 如果使用PagerTabStrip,只需将其添加到ViewPager上方即可,它会自动与...
`TabHost`是Android早期版本中用于实现多标签页切换的类,但在新的Android设计规范中,`ActionBar`或`ToolBar`中的`Tab`已成为更推荐的实现方式。步骤如下: 1. **启用ActionBar**: 首先确保在AndroidManifest.xml...
首先,Android的Tab实现通常采用`TabHost`、`ActionBar`或者`TabLayout`与`ViewPager`的组合。在现代Android开发中,`TabLayout`配合`ViewPager`是推荐的方式,因为它提供了更灵活的定制选项和更好的用户体验。`...
这个"Android应用源码之Tab控件使用的最简纯净Demo"是一个示例项目,旨在帮助开发者理解如何在Android应用中实现简洁、高效的Tab切换功能。通过分析这个Demo,我们可以学习到以下关键知识点: 1. **TabHost与...
`actionBarTab`是`ActionBar`中实现多标签切换功能的一种方式,它帮助用户在不同的视图或内容之间进行导航。本文将深入探讨`ActionBarTab`的使用和相关知识点。 一、`ActionBar`简介 `ActionBar`是Android 3.0...
4. **设置TabLayout**:创建TabLayout需要在XML布局文件中添加`<androidx.tabs.widget.TabLayout>`标签,并设置相应的属性如`tabMode`(控制标签页的显示方式)和`tabGravity`(控制标签页的对齐方式)。同时,...
不过,Android开发中还有更高级的选项卡实现方式,比如使用ActionBar或Toolbar上的TabLayout,以及搭配ViewPager进行更复杂的页面滑动效果。这些方法通常适用于Android 3.0及以上版本,并且提供了更现代的用户体验。...
在Android开发中,`TabActivity`是早期用来实现底部标签栏切换页面的一种方式。随着Android SDK的更新,`TabActivity`已经被废弃,取而代之的是`Fragment`和`ViewPager`的组合,以及Google推出的`TabLayout`与`...
`TabHost`是Android SDK提供的一种组件,用于实现这种多标签页的布局,它可以帮助开发者轻松地创建一个带有多个选项卡的应用界面,每个选项卡对应一个不同的活动或视图。下面将详细介绍如何使用`TabHost`来实现底部...
在Android开发中,`TabHost`是一个非常重要的组件,它被用于实现标签栏(Tab)的布局,使得用户可以通过不同的标签页浏览和切换不同的内容。`TabHost`结合`TabWidget`和`FrameLayout`来创建一个标准的标签布局。在本...
在Android应用开发中,屏幕下方的Tab菜单是一种常见的界面设计,用于展示多个功能模块或内容区域,用户可以通过点击不同的Tab切换不同的页面。本篇文章将详细讲解如何在Android中实现这样的底部Tab菜单,以及涉及到...