1,概念
1)认识
fragment是activity的一个界面或一个组成部分,一个activity可以由多个fragment组成,每个Fragment有自己的生命周期、可接收和处理用户事件,避免了activity有太多繁重的代码。
另外,可以根据用户的不同,动态的添加、替换和移除某个fragment。
2)生命周期
Fragment和Activity生命周期关系:
①onAttach(Activity)
当fragment添加到activity中时调用。
在一个fragment从activity中剥离的时候,调用onDetach方法,把传递进来的activity对象释放掉,不然会影响activity的销毁,产生不必要的错误。
②onCreateView(LayoutInflater, ViewGroup,Bundle)
创建该Fragment的视图
③onActivityCreated(Bundle)
当Activity的onCreate方法返回时调用
④onDestoryView()
与onCreateView想对应,当该Fragment的视图被移除时调用
⑤onDetach()
与onAttach相对应,当Fragment与Activity关联被取消时调用
注意
除了onCreateView,其他的所有方法如果你重写了,必须调用父类对于该方法的实现。
3)Fragment常用API
①android.app.Fragment
这个类用于定义Fragment
②android.app.FragmentManager
主要用于在Activity中操作Fragment。
getFragmentManager() //获取FragmentManage的方式
③android.app.FragmentTransaction
保证一些Fragment操作的原子性。
FragmentTransaction transaction = fm.benginTransatcion();//开启一个事务
transaction.add() //往Activity中添加一个Fragment
transaction.remove() //从Activity中移除一个Fragment,如果被移除的Fragment没有添加到回退栈(回退栈后面会详细说),这个Fragment实例将会被销毁。
transaction.replace() //使用另一个Fragment替换当前的,实际上就是remove()然后add()的合体~
transaction.hide() //隐藏当前的Fragment,仅仅是设为不可见,并不会销毁
transaction.show() //显示之前隐藏的Fragment
detach() //会将view从UI中移除,和remove()不同,此时fragment的状态依然由FragmentManager维护
attach() //重建view视图,附加到UI上并显示。
transatcion.commit()//提交一个事务(commit方法一定要在Activity.onSaveInstance()之前调用)
注意:
replace()使用时,会remove掉当前的fragment,重新初始化一个new fragment进行替换,完全执行新的fragment的生命周期。当且仅当上一个Fragment不再需要时才采用。
一般Fragment的切换应该是:add(),切换时hide()当前的碎片,show()想展示的碎片。
2,与Activity交互
1)Fragment调Activity数据
①调用父Activity中方法
(ParentActivity ) getActivity().test();
②获取Context
getContext()
③接口
在fragment中定义一个内部回调接口,activity实现该回调接口,这样fragment即可调用该回调方法将数据传给activity。
2)Activity给Fragment传递数据
①Bundle传递数据
Bundle bundle = new Bundle();
bundle.putString(Constant.INTENT_ID, productId);
Fragment fragment = null;
switch (position) {
case 0:
fragment = new ProductImageDetailFragment();
break;
case 1:
fragment = new ProductParamFragment();
break;
case 2:
fragment = new ProductCommentFragment();
break;
default:
break;
}
fragment.setArguments(bundle);
Fragment中的onCreatView/onStart()方法中,通过getArgments()方法,获取到bundle对象,然后通过getString的key值拿到我们传递过来的值:
if (isAdded()) {//判断Fragment已经依附Activity
productId = getArguments().getString(Constant.INTENT_ID);
}
②onAttach方法传值
宿主Activity中定义方法,将要传递的值传递到Fragment中,在Fragment中的onAttach方法中,获取到这个值。
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
titles = ((MainActivity) activity).getTitles();//通过强转成宿主activity,就可以获取到传递过来的数据
}
3)Fragment与Fragment交互
①临时存放在Activity中
②用Sharepreference或数据库
3,实现
1)viewPager
①FragmentPagerAdapter
Google基于PagerAdapter添加了FragmentPagerAdapter类来解决Fragment与ViewPager之间的配合问题。
其中有三个全部变量:
private final FragmentManager mFragmentManager;//用于管理Fragment对象
private FragmentTransaction mCurTransaction = null;//当前操作事务
private Fragment mCurrentPrimaryItem = null;//当前主要项
给定的方法:
/**
* 获取给定位置对应的Fragment。
*
* @param position 给定的位置
* @return 对应的Fragment
*/
public abstract Fragment getItem(int position);
/**
* 获取给定位置的项Id,用于生成Fragment名称
*
* @param position 给定的位置
* @return 项Id
*/
public long getItemId(int position) {
return position;
}
/**
* 根据viewId和项Id生成Fragment名称
* @param viewId
* @param id
* @return Fragment名称
*/
private static String makeFragmentName(int viewId, long id) {
return "android:switcher:" + viewId + ":" + id;
}
②demo
i>FragmentAdapter
public class FragmentAdapter extends FragmentPagerAdapter {
private List<Fragment> fragmentList;
private List<String> titleList;
//构造方法一 推荐
public FragmentAdapter(FragmentManager fm, List<Fragment> fragmentList,List<String> titleList) {
super(fm);
this.fragmentList = fragmentList;
this.titleList = titleList;
}
@Override
public Fragment getItem(int position) {
return fragmentList.get(position);
}
@Override
public int getCount() {
return fragmentList.size();
}
@Override
public CharSequence getPageTitle(int position) {
return titleList.get(position);
}
}
ii>activity的xml
使用TabLayout需要先添加依赖:
compile 'com.android.support:design:26.1.0'
<android.support.design.widget.TabLayout
android:id="@+id/coupon_tab_layout"
android:layout_width="match_parent"
android:layout_height="@dimen/dp48"
android:background="@color/white"
app:tabGravity="fill"
android:layout_centerInParent="true"
app:tabIndicatorColor="@color/orange1"
app:tabSelectedTextColor="@color/orange1"/>
<android.support.v4.view.ViewPager/>
注意:
如果要给TabLayout上加图标:
一、用SpannableString 来加入图片
二、TabLayout的textAllCaps属性默认值是true,会阻止ImageSpan的渲染,我们需要将其重写为false。
iii>activity
是基本的AppCompatActivity即可。
//ViewPager viewPager; TabLayout tabLayout;
List<Fragment> fragmentList = new ArrayList<>();
fragmentList.add(new CouponFragment(CouponTypeBean.USE));
fragmentList.add(new CouponFragment(CouponTypeBean.USE_NOT));
List<String> titleList = new ArrayList<>();
titleList.add("可用");
titleList.add("已过期");
FragmentAdapter fragmentAdapter = new FragmentAdapter(getSupportFragmentManager(), fragmentList, titleList);
viewPager.setAdapter(fragmentAdapter);
viewPager.setOffscreenPageLimit(3);
tabLayout.setupWithViewPager(viewPager);
2)底部菜单
①TabLayout
具体使用参考上述demo。
首先依赖:
compile 'com.android.support:design:26.1.0'
<!--
tabIndicatorColor:菜单下方移动的横线的颜色
tabSelectedTextColor :菜单被选中之后的颜色
tabTextColor : 菜单正常的颜色
app:tabTextAppearance : 添加样式,这里加了样式主要是为了在文字前面加一个图所用,就是把textAllCaps设置成false-->
<android.support.design.widget.TabLayout
android:id="@+id/coupon_tab_layout"
android:layout_width="match_parent"
android:layout_height="@dimen/high_45dp"
android:background="@color/white"
app:tabGravity="fill"
android:layout_centerInParent="true"
app:tabIndicatorColor="@color/colorTheme"
app:tabSelectedTextColor="@color/colorTheme"/>
最后,绑定viewPager
tabLayout.setupWithViewPager(viewPager);
屏蔽TabLayout点击事件:
LinearLayout tabStrip = (LinearLayout) tabLayout.getChildAt(0);
for (int i = 0; i < tabStrip.getChildCount(); i++) {
View tabView = tabStrip.getChildAt(i);
if (tabView != null) {
tabView.setClickable(false);
}
}
②TextView实现
外面套一个LinerLayout,里面3个TextView。通过设置权值让3个按钮宽度一样,TextView可以同时设置DrawableTop属性和Text属性。
③TabHost
④RadioButton
可通过StateListDrawable来实现图片、字体颜色的切换。
4,加载方式
1)预加载
①介绍
viewpager里的fragment进行了前后预加载,即3个fragment的数据加载。
通过setOffscreenPageLimit来设置预加载的项目,不设置setOffscreenPageLimit,则默认为1(设置0无效,可以查看该方法源码知道),即时设置了让他预加载为0了,也会执行多个预加载。
②优点
优化用户体验。
③缺点
降低了性能,浪费了初始化性能。
2)懒加载
①介绍
即只加载当前显示页面且只加载一次,滑动到其他页面时才加载其他页面数据,当再滑动到已加载过数据的页面时不再进行数据加载操作,若想要刷新数据,再调用相应的加载数据方法。
②优缺点
优点:减少了网络请求次数。
缺点:适合依赖数据的界面(即展示内容较多的,比如ListView,gridview。除此之外,对于复杂布局比较吃内存)
③原理
i>setUserVisibleHint()对fragment的可见状态监测:
当isVisibleToUser 为true则进行数据加载,当isVisibleToUser为false则不进行数据加载;
这个方法在oncreateView()前执行。
注意加载时,如果这个fragment不可见的加载,那setUserVisibleHint()没有执行。
ii>对于已经加载过数据的fragment,再次被滑动到也不再进行加载数据,也就是每个fragment仅做一次数据加载工作。
④BaseFragment
public abstract class BaseFragment extends Fragment {
private boolean isVisible = false;//当前Fragment是否可见
private boolean isInitView = false;//是否与View建立起映射关系
private boolean isFirstLoad = true;//是否是第一次加载数据
private View convertView;
private SparseArray<View> mViews;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
LogUtil.m(" " + this.getClass().getSimpleName());
convertView = inflater.inflate(getLayoutId(), container, false);
mViews = new SparseArray<>();
initView();
isInitView = true;
lazyLoadData();
return convertView;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
LogUtil.m(" " + this.getClass().getSimpleName());
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
LogUtil.m("context" + " " + this.getClass().getSimpleName());
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
LogUtil.m("isVisibleToUser " + isVisibleToUser + " " + this.getClass().getSimpleName());
if (isVisibleToUser) {
isVisible = true;
lazyLoadData();
} else {
isVisible = false;
}
super.setUserVisibleHint(isVisibleToUser);
}
private void lazyLoadData() {
if (isFirstLoad) {
LogUtil.m("第一次加载 " + " isInitView " + isInitView + " isVisible " + isVisible + " " + this.getClass().getSimpleName());
} else {
LogUtil.m("不是第一次加载" + " isInitView " + isInitView + " isVisible " + isVisible + " " + this.getClass().getSimpleName());
}
if (!isFirstLoad || !isVisible || !isInitView) {
LogUtil.m("不加载" + " " + this.getClass().getSimpleName());
return;
}
LogUtil.m("完成数据第一次加载");
initData();
isFirstLoad = false;
}
/**
* 加载页面布局文件
* @return
*/
protected abstract int getLayoutId();
/**
* 让布局中的view与fragment中的变量建立起映射
*/
protected abstract void initView();
/**
* 加载要显示的数据
*/
protected abstract void initData();
/**
* fragment中可以通过这个方法直接找到需要的view,而不需要进行类型强转
* @param viewId
* @param <E>
* @return
*/
protected <E extends View> E findView(int viewId) {
if (convertView != null) {
E view = (E) mViews.get(viewId);
if (view == null) {
view = (E) convertView.findViewById(viewId);
mViews.put(viewId, view);
}
return view;
}
return null;
}
}
5,Fragment重叠问题
1)问题
当该Activity由于长时间占用内存或者由于内存不足时,而被系统收回内存。
系统销毁掉该Activity后,重新启动该Activity而出现重叠问题。
2)解决办法
利用onSaveInstanceState()函数的特性,当Activity被系统销毁掉时,保存当前Activity中显示Fragment的TAG值。若启动时,再show被保存TAG的Fragment。
6,Demo
1)用TextView实现的底部菜单,以及Fragment的应用
点击下载demo
早期代码,仅做参考,不推荐使用。
2)ViewPager实现fragment的切换
7,ViewPager
1)概念
ViewPager是android扩展包v4包中的类:android.support.v4.view.ViewPager。
ViewPager类直接继承了ViewGroup类,和LinearLayout等布局一样,都是一个容器。
ViewPager类需要PagerAdapter适配器类提供数据,与ListView类似。
2)场景
①当需要加载的页卡是View
LayoutInflater lf = getLayoutInflater().from(this);
view1 = lf.inflate(R.layout.layout1, null);
view2 = lf.inflate(R.layout.layout2, null);
view3 = lf.inflate(R.layout.layout3, null);
viewList = new ArrayList<View>();// 将要分页显示的View装入数组中
viewList.add(view1);
viewList.add(view2);
viewList.add(view3);
用ViewPagerAdapter:
public class MyViewPagerAdapter extends PagerAdapter{
private List<View> mListViews;
public MyViewPagerAdapter(List<View> mListViews) {
this.mListViews = mListViews;//构造方法,参数是我们的页卡,这样比较方便。
}
//直接继承PagerAdapter,至少必须重写下面的四个方法,否则会报错
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView(mListViews.get(position));//删除页卡
}
@Override
public Object instantiateItem(ViewGroup container, int position){
//这个方法用来实例化页卡
container.addView(mListViews.get(position), 0);//添加页卡
return mListViews.get(position);
}
@Override
public int getCount() {
return mListViews.size();//返回页卡的数量
}
@Override
public boolean isViewFromObject(View arg0, Object arg1) {
return arg0==arg1;//官方提示这样写
}
}
②当需要加载的页卡是Fragment
在讲Fragment的时候已经说过,这里不多做赘述。
用FragmentAdapter,需继承FragmentPagerAdapter。
3)FragmentStatePagerAdapter 和 FragmentPagerAdapter
即当拥有大量的页面时,使用FragmentStatePagerAdapter不必在内存中占用大量的内存
①同
PageAdapter 是 FragmentPagerAdapter 以及
FragmentStatePagerAdapter 的基类,可将上面的FragmentPagerAdapter 替换成FragmentStatePagerAdapter。
②异
FragmentPagerAdapter使用时,每一个生成的 Fragment 都将保存在内存之中,而 FragmentStatePagerAdapter 只保留了当前显示的Fragment,其他划过的Fragment离开视线后,就会被销毁;而在页面需要显示时,再生成新的实例。
③场景
i>FragmentPagerAdapter
继承自 PagerAdapter。该类内的每一个生成的 Fragment 都将保存在内存之中,因此适用于那些相对静态的页,数量也比较少的那种;如果需要处理有很多页,并且数据动态性较大、占用内存较多的情况,应该使用FragmentStatePagerAdapter。
ii>FragmentStatePagerAdapter
继承自 PagerAdapter。和 FragmentPagerAdapter 不一样的是,正如其类名中的 ‘State’ 所表明的含义一样,该 PagerAdapter 的实现将只保留当前页面,当页面离开视线后,就会被消除,释放其资源;而在页面需要显示时,生成新的页面(就像 ListView 的实现一样)。这么实现的好处就是当拥有大量的页面时,不必在内存中占用大量的内存。
8,滑动冲突问题
1)ScrollView 中嵌套ViewPager冲突
如果ViewPager高度使用wrap_content,就会出现无法显示问题。
解决方案:
1.给ViewPager 设定固定高度(不推荐)
2.自定义ViewPager 重写ViewPager测量方法
public class CustomViewPager extends ViewPager {
public CustomViewPager(Context context) {
super(context);
}
public CustomViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int height = 0;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
int h = child.getMeasuredHeight();
if (h > height)height = h;
}
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}