当前位置: 代码迷 >> Android >> 【安卓札记】ViewPager+Fragment布局中的Fragment数据更新
  详细解决方案

【安卓札记】ViewPager+Fragment布局中的Fragment数据更新

热度:118   发布时间:2016-04-28 06:27:30.0
【安卓笔记】ViewPager+Fragment布局中的Fragment数据更新
-------------------------------------------------------------------------------------
请尊重作者劳动成果,转载请声明文章出处(http://blog.csdn.net/chdjj/)
-------------------------------------------------------------------------------------
相信大家对ViewPager和Fragment都比较熟悉了。使用ViewPager+Fragment可以实现”选项卡“布局,通过左右滑动屏幕切换选项卡。
但是有些场景,我们的Fragment中的内容不是固定的,甚至布局都不是固定的,这时我们需要动态更新Fragment的数据或布局。所以本文将介绍更新Fragment数据的一种方法(可能不是最好的,如果大家有更好的方法一定要跟我说啊~)。
首先我们快速实现下“选项卡”切换效果。
注:为了简单起见,我们不加选项卡的标题。
步骤很简单,在activity布局中创建一个ViewPager节点,为ViewPager设置适配器(PagerAdapter),适配器产生数据填充ViewPager。
Activity布局:
<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=".MainActivity" >     <android.support.v4.view.ViewPager        android:id="@+id/viewpager"        android:layout_width="fill_parent"        android:layout_height="fill_parent" /></LinearLayout>
主界面只有一个ViewPager节点。
下面创建3个Fragment:
package com.example.viewpagerdemo2;import android.os.Bundle;import android.support.v4.app.Fragment;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;public class Tab3 extends Fragment{    @Override    public View onCreateView(LayoutInflater inflater, ViewGroup container,            Bundle savedInstanceState)    {        return inflater.inflate(R.layout.tab3,null);    }}
代码很简单,直接在oncreateView方法中使用布局填充器(LayoutInflater)填充一个View布局即可。
布局如下:
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:background="#0000ff"    android:orientation="vertical" >        <TextView         android:id="@+id/tab1"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:text="我是第三个界面"        /></LinearLayout>
其他两个Fragment跟这个一模一样,这里就不贴了。
Fragment都创建好之后,我们来写Activity的逻辑,我们需要为ViewPager指定一个PagerAdapter。
google为我们提供了方便的类叫FragmentPagerAdapter,我们只需继承这个类并复写getItem和getCount即可。
MainActivity如下:
package com.example.viewpagerdemo2;import android.os.Bundle;import android.support.v4.app.Fragment;import android.support.v4.app.FragmentActivity;import android.support.v4.app.FragmentManager;import android.support.v4.app.FragmentPagerAdapter;import android.support.v4.view.ViewPager;public class MainActivity extends FragmentActivity{    private ViewPager vPager = null;    private static final int TAB_COUNT = 3;    @Override    protected void onCreate(Bundle savedInstanceState)    {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        vPager = (ViewPager) findViewById(R.id.viewpager);        vPager.setAdapter(new MyPagerAdapter(getSupportFragmentManager()));    }    public class MyPagerAdapter extends FragmentPagerAdapter    {        public MyPagerAdapter(FragmentManager fm)        {            super(fm);        }        @Override        public Fragment getItem(int position)        {            switch (position)            {            case 0:                return new Tab1();            case 1:                return new Tab2();            case 2:                return new Tab3();            }            return null;        }        @Override        public int getCount()        {            return TAB_COUNT;        }    }}
代码很简单,就不过多解释了。我在FragmentPagerAdapter的getItem方法中根据position直接new出Fragment对象。
效果如下:
在往下讲之前,有必要让大家了解下FragmentPagerAdapter的特性:
这段话摘自文档,上面说用户每访问到一个选项卡,代表该选项卡的Fragment对象会被保存到内存中(缓存),这样做的目的自然是节省资源并提高响应速度。但是带来的问题是当我们Fragment的数据发生改变时如何提醒系统重新创建Fragment对象呢?
大家可能会说FragmentPagerAdapter继承自PagerAdapter,而PagerAdapter有个notifiyDataSetChanged方法用于通知系统数据发生改变需更新视图。下面我们就测试下这个方法是否管用。
依然使用上面的代码,稍微修改下。
我想实现这样的效果,当我们点击选项卡2中的一个按钮时,更改选项卡1的布局。
注:如果仅仅修改选项卡1中textView的文字,其实可以使用下面这行代码实现,即在Fragment中通过获取FragmentManager来间接获得另一个Fragment实例,然后调用这个实例的方法设置文本。
getActivity().getSupportFragmentManager().findFragmentById(R.id.fg1);
首先我们在Mainctivity中添加一个方法,用于获取Adapter:
/**     * 获取适配器     * @return     */    public MyPagerAdapter getAdapter()    {        return adapter;    }
这里先贴出更新的布局:
tab_new.xml
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:background="#ff0000"    android:orientation="vertical" >        <TextView         android:id="@+id/tab1"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:text="我是第一个界面--->已更新"        />    <TextView         android:id="@+id/tab1_ss"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:text="我是第一个界面--->已更新"        /></LinearLayout>
然后在选项卡2中添加一个按钮,布局就不贴了:
package com.example.viewpagerdemo2;import android.content.Context;import android.content.SharedPreferences;import android.content.SharedPreferences.Editor;import android.os.Bundle;import android.support.v4.app.Fragment;import android.util.Log;import android.view.LayoutInflater;import android.view.View;import android.view.View.OnClickListener;import android.view.ViewGroup;import android.widget.Button;import android.widget.Toast;public class Tab2 extends Fragment{    private static final String TAG = "Tab2";    private Button but = null;    @Override    public View onCreateView(LayoutInflater inflater, ViewGroup container,            Bundle savedInstanceState)    {        View view = inflater.inflate(R.layout.tab2,null);        but = (Button) view.findViewById(R.id.but);        Log.i(TAG,"TAB2 CREATED...");        return view;    }    @Override    public void onActivityCreated(Bundle savedInstanceState)    {        super.onActivityCreated(savedInstanceState);        but.setOnClickListener(new OnClickListener()        {            @Override            public void onClick(View v)            {                SharedPreferences sp = Tab2.this.getActivity().getSharedPreferences(Tab1.CONTENT_VIEW,Context.MODE_PRIVATE);                boolean state = sp.getBoolean(Tab1.IS_UPDATE, false);                Toast.makeText(getActivity(),state+"",0).show();                Editor editor = sp.edit();                editor.putBoolean(Tab1.IS_UPDATE,!state);                editor.commit();                MainActivity a = (MainActivity) getActivity();                a.getAdapter().notifyDataSetChanged();            }        });    }}
按钮的响应事件执行这样的逻辑:当点击按钮时,先去sharepref中寻找某个属性,如果该属性为true,那就写回false,属性为false,那就写回true。最后调用notifyDataSetChanged方法。
最后我们看下选项卡1的逻辑:
package com.example.viewpagerdemo2;import android.content.Context;import android.content.SharedPreferences;import android.os.Bundle;import android.support.v4.app.Fragment;import android.util.Log;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;public class Tab1 extends Fragment{    public static final String CONTENT_VIEW = "content_view";    public static final String IS_UPDATE = "is_update";    private static final String TAG = "Tab1";    @Override    public View onCreateView(LayoutInflater inflater, ViewGroup container,            Bundle savedInstanceState)    {        Log.i(TAG,"TAB1 CREATED...");        SharedPreferences sp =getActivity().getSharedPreferences(CONTENT_VIEW,Context.MODE_PRIVATE);        boolean is_update = sp.getBoolean(IS_UPDATE,false);        View view = null;        if(is_update)        {            view = inflater.inflate(R.layout.tab1_new, null);        }else        {            view = inflater.inflate(R.layout.tab1,null);        }        return view;     }}
选项卡1从sharepref中获取该属性,然后根据该属性的值设置布局。这样我们就完成了整个代码的修改。下面测试下:
界面2布局:
点击按钮前:

点击按钮后:
很遗憾,并没有实现我们的效果(原因我也不清楚,有知道的告诉我一声啊)。
然而,比较有趣的是当我们点击按钮后,并不马上滑回第一个界面,而是先滑到第三个界面,再滑到第一个界面,惊奇的发现,布局改变了:
这里可能是GC回收掉了代表选项卡1的Fragment1对象,这时当我们滑会第一个界面时,重新创建了Fragment。这显然不是我们期望的效果。

下面说下我的解决方案:
适配器中应该提供一个设置适配器数据的方法,这个方法可以向适配器填充新的数据,并remove掉旧的数据。
按照这个思路,我们重构下MyPagerAdapter类:
首先加上这个成员:
  /**     * 页面内容集合     */    private List<Fragment> fgs = null;
然后为其增加set方法:
 /**     * 重新设置页面内容     * @param items     */    public void setPagerItems(List<Fragment> items)    {        if (items != null)        {            for (int i = 0; i < fgs.size(); i++)            {                mFragmentManager.beginTransaction().remove(fgs.get(i)).commit();            }            fgs = items;        }    }
这样当数据改变时,我们仅需调用setPagerItems方法即可重新设置数据。
但是考虑到刚才的需求是在一个Fragment中修改另一个Fragment布局,在Fragment中调用setPagerItems似乎并不是很优雅,因为该Fragment并不应该知道父视图中有哪些选项卡(Fragment),故而我们应该让Activity调用setPagerItems方法。这时我们可以这样做:
在适配器中添加一个回调接口:
/**     * @author Rowand jj     *回调接口     */    public interface OnReloadListener    {        public void onReload();    }
再添加一个设置回调接口的方法:
public void setOnReloadListener(OnReloadListener listener)    {        this.mListener = listener;    }
最后再提供一个reLoad方法:
/**     *当页面数据发生改变时你可以调用此方法     *      * 重新载入数据,具体载入信息由回调函数实现     */    public void reLoad()    {        if(mListener != null)        {            mListener.onReload();        }         this.notifyDataSetChanged();//不可少,通知系统数据改变    }
Activity在设置适配器时,先为适配器实现添加一个回调函数,在回调方法中调用setPagerItems方法重新设置数据。
adapter.setOnReloadListener(new OnReloadListener()        {            @Override            public void onReload()            {                fgs = null;                List<Fragment> list = new ArrayList<Fragment>();                list.add(new Tab1());                list.add(new Tab2());                list.add(new Tab3());                adapter.setPagerItems(list);            }        });
再刚才的界面2的Fragment的按钮响应事件中调用:
  MainActivity a = (MainActivity) getActivity();    a.getAdapter().reLoad();
即可更新布局,图就不贴了。

最后贴出完整的MyPagerAdapter源码和MainActivity的源码:
MyPagerAdapter:
package com.example.viewpagerdemo2;import java.util.List;import android.support.v4.app.Fragment;import android.support.v4.app.FragmentManager;import android.support.v4.app.FragmentPagerAdapter;import android.util.Log;/** * @author Rowand jj * 页面适配器 */public class MyPagerAdapter extends FragmentPagerAdapter{    private static final String TAG = "YiPageAdapter";    /**     * 页面内容集合     */    private List<Fragment> fgs = null;    private FragmentManager mFragmentManager;    /**     * 当数据发生改变时的回调接口     */    private OnReloadListener mListener;    public MyPagerAdapter(FragmentManager fm, List<Fragment> fgs)    {        super(fm);        this.fgs = fgs;        mFragmentManager = fm;    }    @Override    public Fragment getItem(int index)    {        Log.i(TAG,"ITEM CREATED...");        return fgs.get(index);    }    @Override    public int getCount()    {        return fgs.size();// 返回选项卡总数    }    @Override    public int getItemPosition(Object object)    {        return POSITION_NONE;    }    /**     * 重新设置页面内容     * @param items     */    public void setPagerItems(List<Fragment> items)    {        if (items != null)        {            for (int i = 0; i < fgs.size(); i++)            {                mFragmentManager.beginTransaction().remove(fgs.get(i)).commit();            }            fgs = items;        }    }    /**     *当页面数据发生改变时你可以调用此方法     *      * 重新载入数据,具体载入信息由回调函数实现     */    public void reLoad()    {        if(mListener != null)        {            mListener.onReload();        }        this.notifyDataSetChanged();    }    public void setOnReloadListener(OnReloadListener listener)    {        this.mListener = listener;    }    /**     * @author Rowand jj     *回调接口     */    public interface OnReloadListener    {        public void onReload();    }}
MainActivity:
package com.example.viewpagerdemo2;import java.util.ArrayList;import java.util.List;import android.os.Bundle;import android.support.v4.app.Fragment;import android.support.v4.app.FragmentActivity;import android.support.v4.view.ViewPager;import com.example.viewpagerdemo2.MyPagerAdapter.OnReloadListener;public class MainActivity extends FragmentActivity{    private ViewPager vPager = null;    private static final int TAB_COUNT = 3;    private MyPagerAdapter adapter = null;    private List<Fragment> fgs = null;    @Override    protected void onCreate(Bundle savedInstanceState)    {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);                vPager = (ViewPager) findViewById(R.id.viewpager);        fgs = new ArrayList<Fragment>();        fgs.add(new Tab1());        fgs.add(new Tab2());        fgs.add(new Tab3());        adapter = new MyPagerAdapter(getSupportFragmentManager(), fgs);        adapter.setOnReloadListener(new OnReloadListener()        {            @Override            public void onReload()            {                fgs = null;                List<Fragment> list = new ArrayList<Fragment>();                list.add(new Tab1());                list.add(new Tab2());                list.add(new Tab3());                adapter.setPagerItems(list);            }        });        vPager.setAdapter(adapter);    }        /**     * 获取适配器     * @return     */    public MyPagerAdapter getAdapter()    {        return adapter;    }}
仅仅是提供一种思路,如果大家有好的思路请留言。











2楼sunzheng_123昨天 22:30
android交流群 343495441
1楼minter01前天 23:56
不刷新的原因时因为 切换的时候fragment并没有被干掉 再切过来的时候连resume都不用调用。因为fragment是保留在堆栈中的。你可以打下生命周期的log看看就知道了。不过我是来借鉴你的刷新数据的方法的。顶个!
Re: RowandJJ昨天 14:11
回复minter01n哦,是这样啊,谢谢~
  相关解决方案