【android学习】Fragment和ViewPager

1,概念

1)认识

fragment是activity的一个界面或一个组成部分,一个activity可以由多个fragment组成,每个Fragment有自己的生命周期、可接收和处理用户事件,避免了activity有太多繁重的代码。
另外,可以根据用户的不同,动态的添加、替换和移除某个fragment。

2)生命周期

fragment生命周期

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的切换

点击下载demo

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);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值