-------------------------------------------------------------------------------------
请尊重作者劳动成果,转载请声明文章出处(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哦,是这样啊,谢谢~