Android使用Fragment打造万能页面切换框架

首先我们来回忆一下传统用Activity进行的页面切换,activity之间切换,首先需要新建intent对象,给该对象设置一些必须的参数,然后调用startActivity方法进行页面跳转。如果需要activity返回结果,则调用startActivityForResult方法,在onActivityResult方法中获得返回结果。此外,每一个要展示的activity需要在AndroidManifest.xml文件中注册。而且,如果在某些特定的情况下(比如65536方法数爆炸)要动态加载dex,还得手动管理activity的生命周期。那么,有没有这么一种方法进行页面切换时,无需在AndroidManifest.xml文件中声明这些信息,动态加载时又无需我们管理生命周期,等等优点呢。

我们来回忆一下,在android3.0之后,谷歌出了一个Fragment,这个东西依赖于activity,其生命周期由宿主activity进行管理,并且可以通过FragmentManager和FragmentTransaction等相关的类进行管理。那么我们能不能从Fragment入手,打造一个完全由Fragment组成的页面跳转框架呢。

使用Fragment其实很简单,首先开启一个事务,通过add,replace,remove等方法进行添加,替换,移除等操作,这一切的操作可能需要依赖一个容器,这个容器提供一个id,进行对应操作时将这个id作为参数传入。之后通过相应方法提交事务就可以了,就像这样子。

FragmentManager fragmentManager = getSupportFragmentManager();  FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();  fragmentTransaction.replace(R.id.fragment_container, fragment);  fragmentTransaction.commit(); 

然而我相信你一定有这样的经历,在使用Fragment进行页面切换时又得不断用代码控制其显示与隐藏的逻辑,那么有没有这样一种方法在程序中不断复用这段代码呢?

首先,我们希望Fragment能像Activity那样,进行正确的跳转。那么需要什么,答案是Fragment对象,我们肯定需要它的Class全类名,当然跳转的时候可能会带上一些参数,这个参数应该通过Bundle进行传递。而且,全类名可能太长,不便记忆,我们参考web的架构,应该还要取一个别名alias。就这样,一个Fragment页面的三个基本属性就被我们抽取出来了,组成了如下的实体类。在这个实体类中,页面传递的参数为json形式的String字符串对象,在需要使用的时候我们通过该json构造出bundle。页面名变量mName是整个程序唯一标示该页面的参数,其值唯一,但是其对应的class全类名可以不唯一,也就是说从name到class的映射可以一对多。

public class CorePage implements Serializable { private static final long serialVersionUID = 3736359137726536495L; private String mName; //页面名 private String mClazz; //页面class private String mParams; //传入参数,json object结构 public CorePage(String name, String clazz, String params) { mName = name; mClazz = clazz; mParams = params; } public String getClazz() { return mClazz; } public void setClazz(String clazz) { mClazz = clazz; } public String getName() { return mName; } public void setName(String name) { mName = name; } public String getParams() { return mParams; } public void setParams(String params) { mParams = params; } @Override public String toString() { return "Page{" + "mName='" + mName + '\'' + ", mClazz='" + mClazz + '\'' + ", mParams='" + mParams + '\'' + '}'; } }

实体类编写好了,为了更方便的进行页面跳转,我们需要像Activity那样,有一个配置文件,里面存着Fragment名到其全类名的映射关系。那么这些数据存在哪呢。我们参考网络数据,一般从网络上获取的数据有两种格式,一种是json,一种是xml,json由于其优点,在网络传输中被大量使用,这里,我们优先使用json,选定了json之后,就要选定一个json解析的框架,我们不使用android系统自带的,我们使用阿里的fastjson,当然你也可以使用gson或者jackson。我们的Fragment有很多,所以这个Fragment的配置文件应该是一个json数组。就像这个样子

[ { "name": "test1", "class": "cn.edu.zafu.corepage.sample.TestFragment1", "params": { "param1": "value1", "param2": "value2" } }, { "name": "test2", "class": "cn.edu.zafu.corepage.sample.TestFragment2", "params": "" } ]

有了这个配置,我们就要在程序进入时读取这个配置。我们将这个配置放在assets目录下,当然你也可以放在其他目录下,只有你能读取到就行,甚至你可以放在压缩包里。然而,实际情况下,这个文件不应该暴露,因为一旦暴露就存在风险。因此,大家可以采用更安全的方式存这些数据,比如把数据压缩到压缩包中,增加一个密码,而读取文件的内容的代码我们移到native中取实现,毕竟java层太容易被反编译了,而c/c++层相对来说会毕竟有难度。

这里为了简单,我们暂时放在assets目录下,那么要从assets目录中读取这个文件内容就必须有这么一个读取该目录下文件的函数,该目录在android中也算是一个比较特殊的目录了,可以通过getAssets()函数,然后获得一个输入流,将文件内容读出,然后将对应的json解析出来就可以了。

/** * 从assets目录下读取文件 * * @param context 上下文 * @param fileName 文件名 * @return */ private String readFileFromAssets(Context context, String fileName) { String result = ""; try { InputStreamReader inputReader = new InputStreamReader(context.getResources().getAssets().open(fileName)); BufferedReader bufReader = new BufferedReader(inputReader); String line = ""; while ((line = bufReader.readLine()) != null) result += line; } catch (Exception e) { e.printStackTrace(); } return result; }

然后根据该文件内容读取json配置。读取出来后需要将这些数据保存下来。因此要有一个数据结构来保存这个对象,存完之后还要方便取出,存取的依据应该是Fragment的表示,即前面提到的name,因此Map这个数据结构是最适合不过了。

private Map<String, CorePage> mPageMap = new HashMap<String, CorePage>(); //保存page的map

将配置读取出来存进该map,读取的时候判断name和class是否为空,为空则跳过。

/** * 从配置文件中读取page */ private void readConfig() { Log.d(TAG, "readConfig from json"); String content = readFileFromAssets(mContext, "page.json"); JSONArray jsonArray = JSON.parseArray(content); Iterator<Object> iterator = jsonArray.iterator(); JSONObject jsonPage = null; String pageName = null; String pageClazz = null; String pageParams = null; while (iterator.hasNext()) { jsonPage = (JSONObject) iterator.next(); pageName = jsonPage.getString("name"); pageClazz = jsonPage.getString("class"); pageParams = jsonPage.getString("params"); if (TextUtils.isEmpty(pageName) || TextUtils.isEmpty(pageClazz)) { Log.d(TAG, "page Name is null or pageClass is null"); return; } mPageMap.put(pageName, new CorePage(pageName, pageClazz, pageParams)); Log.d(TAG, "put a page:" + pageName); } Log.d(TAG, "finished read pages,page size:" + mPageMap.size()); }

此外,除了从配置文件中读取,我们应该可以动态添加,对外提供这个函数。

/** * 新增新页面 * * @param name 页面名 * @param clazz 页面class * @param params 页面参数 * @return 是否新增成功 */ public boolean putPage(String name, Class<? extends BaseFragment> clazz, Map<String, String> params) { if (TextUtils.isEmpty(name) || clazz == null) { Log.d(TAG, "page Name is null or pageClass is null"); return false; } if (mPageMap.containsKey(name)) { Log.d(TAG, "page has already put!"); return false; } CorePage corePage = new CorePage(name, clazz.getName(), buildParams(params)); Log.d(TAG, "put a page:" + name); return true; } /** * 从hashMap中得到参数的json格式 * * @param params 页面map形式参数 * @return json格式参数 */ private String buildParams(Map<String, String> params) { if (params == null) { return ""; } String result = JSON.toJSONString(params); Log.d(TAG, "params:" + result); return result; }

文章开头已经说了,页面跳转的参数是json形式的字符串,我们还要这么一个函数,能够根据json字符串构造出一个bundle

/** * 根据page,从pageParams中获得bundle * * @param corePage 页面 * @return 页面的参数 */ private Bundle buildBundle(CorePage corePage) { Bundle bundle = new Bundle(); String key = null; Object value = null; if (corePage != null && corePage.getParams() != null) { JSONObject j = JSON.parseObject(corePage.getParams()); if (j != null) { Set<String> keySet = j.keySet(); if (keySet != null) { Iterator<String> ite = keySet.iterator(); while (ite.hasNext()) { key = ite.next(); value = j.get(key); bundle.putString(key, value.toString()); } } } } return bundle; }

以上配置读取的一系列函数,构成了页面管理类CorePageManager,我们对其应用单例模式。

/** * 跳转页面管理 */ public class CorePageManager { private volatile static CorePageManager mInstance = null; //单例 private Context mContext; //Context上下文 /** * 构造函数私有化 */ private CorePageManager() { } /** * 获得单例 * * @return PageManager */ public static CorePageManager getInstance() { if (mInstance == null) { synchronized (CorePageManager.class) { if (mInstance == null) { mInstance = new CorePageManager(); } } } return mInstance; } /** * 初始化配置 * * @param context 上下文 */ public void init(Context context) { try { mContext = context.getApplicationContext(); readConfig(); } catch (Exception e) { e.printStackTrace(); } } }

其中init函数暴露给程序入口,进行配置文件的读取。一般放在Application的子类的onCreate方法中即可。

到这里为止,基本上我们一切已经就绪了。就差如何切换了。这里,在CorePageManager类中再提供两个核心函数,用于处理页面切换。

下面这个函数是页面切换的核心函数,首先根据参数从map中拿到对应的实体类,假设存在这个页面,通过class名用反射获得该Fragment对象,调用前面写好的创建Bundle的函数得到页面参数,并与当前函数中的入参bundle进行合并。将参数设置给fragment对象,开启一个fragment事务,查找id为fragment_container的fragment容器,如果该容器已经有fragment,则隐藏它,如果该函数传递了动画参数,则添加页面切换动画,然后将反射获得的fragment对象添加到该容器中,如果需要添加到返回栈,则调用addToBackStack,最后提交事务并返回该fragment对象。

整个函数很简单,就是我们平常在activity中写的切换fragment的代码

/** * 页面跳转核心函数之一 * 打开一个fragemnt * * @param fragmentManager FragmentManager管理类 * @param pageName 页面名 * @param bundle 参数 * @param animations 动画类型 * @param addToBackStack 是否添加到返回栈 * @return */ public Fragment openPageWithNewFragmentManager(FragmentManager fragmentManager, String pageName, Bundle bundle, int[] animations, boolean addToBackStack) { BaseFragment fragment = null; try { CorePage corePage = this.mPageMap.get(pageName); if (corePage == null) { Log.d(TAG, "Page:" + pageName + " is null"); return null; } fragment = (BaseFragment) Class.forName(corePage.getClazz()).newInstance(); Bundle pageBundle = buildBundle(corePage); if (bundle != null) { pageBundle.putAll(bundle); } fragment.setArguments(pageBundle); fragment.setPageName(pageName); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); if (animations != null && animations.length >= 4) { fragmentTransaction.setCustomAnimations(animations[0], animations[1], animations[2], animations[3]); } Fragment fragmentContainer = fragmentManager.findFragmentById(R.id.fragment_container); if (fragmentContainer != null) { fragmentTransaction.hide(fragmentContainer); } fragmentTransaction.add(R.id.fragment_container, fragment, pageName); if (addToBackStack) { fragmentTransaction.addToBackStack(pageName); } fragmentTransaction.commitAllowingStateLoss(); //fragmentTransaction.commit(); } catch (Exception e) { e.printStackTrace(); Log.d(TAG, "Fragment.error:" + e.getMessage()); return null; } return fragment; }

而上面这个函数中的id值在一个基础的布局中,之后的Fragment都会添加到该布局中去。我们的基类Activity也将使用这个布局,这个后续编写BaseActivity的时候会提到

<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/fragment_container" android:layout_width="fill_parent" android:layout_height="fill_parent" > </FrameLayout>

此外,我们再提供一个核心函数。就是如果返回栈中存在了目标fragment,则将其弹出,否则新建fragment打开。

/** * 页面跳转核心函数之一 * 打开一个Fragement,如果返回栈中有则出栈,否则新建 * * @param fragmentManager FragmentManager管理类 * @param pageName 页面别名 * @param bundle 参数 * @param animations 动画 * @return 成功跳转到的fragment */ public Fragment gotoPage(FragmentManager fragmentManager, String pageName, Bundle bundle, int[] animations) { Log.d(TAG, "gotoPage:" + pageName); Fragment fragment = null; if (fragmentManager != null) { fragment = fragmentManager.findFragmentByTag(pageName); } if (fragment != null) { fragmentManager.popBackStackImmediate(pageName, 0); } else { fragment = this.openPageWithNewFragmentManager(fragmentManager, pageName, bundle, animations, true); } return fragment; }

细心的你可能已经注意到了页面跳转函数中用到了动画,其实这个动画是一个数组,为了方便使用,我们将其封装为枚举类,提供常见的几种动画形式。

package cn.edu.zafu.corepage.core; /** * 页面切换动画类别 */ public enum CoreAnim { none, /* 没有动画 */ present, /*由下到上动画 */ slide,/* 从左到右动画 */ fade;/*渐变 */ }

之后我们还要根据该枚举类获得对应的动画的xml文件。

/** * 动画转化,根据枚举类返回int数组 * * @param coreAnim * @return */ public static int[] convertAnimations(CoreAnim coreAnim) { if (coreAnim == CoreAnim.present) { int[] animations = {R.anim.push_in_down, R.anim.push_no_ani, R.anim.push_no_ani, R.anim.push_out_down}; return animations; } else if (coreAnim == CoreAnim.fade) { int[] animations = {R.anim.alpha_in, R.anim.alpha_out, R.anim.alpha_in, R.anim.alpha_out}; return animations; } else if (coreAnim == CoreAnim.slide) { int[] animations = {R.anim.slide_in_right, R.anim.slide_out_left, R.anim.slide_in_left, R.anim.slide_out_right}; return animations; } return null; }

这里贴出一个alpha_in.xml中的代码,其他文件类似,这些文件都位于res/anim目录下

<?xml version="1.0" encoding="utf-8"?> <alpha xmlns:android="http://schemas.android.com/apk/res/android" android:duration="@android:integer/config_mediumAnimTime" android:fromAlpha="0.0" android:toAlpha="1.0" />

到了这里,如果你都明白了,那么后面基本上就没有什么难度了,因为之后的功能都是基于以上内容。

前面我们定义了一个CorePage实体类用于保存配置文件中实体类的信息,而页面切换过程中需要传递一些参数,比如是否添加到fragment返回栈,是否在新的activity中打开fragment,页面切换时的动画,传递的参数等等,通样,我们将其封装为实体类。由于该对象可能需要通过intent传递,这里我们将其实现Parcelable接口。实现该接口方法很简单,假设使用的是android studio,使用快捷键alt+insert选择Parcelable即可创建一个模板,我们将其补齐就好了。整个类如下,我们对外提供了多个重载的构造函数,其本质都是一样的,而前面的动画转换函数我们将其放入这个类中。

/** * 页面跳转控制参数 */ public class CoreSwitchBean implements Parcelable { public static final Parcelable.Creator<CoreSwitchBean> CREATOR = new Parcelable.Creator<CoreSwitchBean>() { @Override public CoreSwitchBean createFromParcel(Parcel in) { return new CoreSwitchBean(in); } @Override public CoreSwitchBean[] newArray(int size) { return new CoreSwitchBean[size]; } }; private String mPageName; //页面名 private Bundle mBundle; //相关数据 private int[] mAnim = null; //动画类型 private boolean mAddToBackStack = true; //是否添加到栈中 private boolean mNewActivity = false; //是否起新的Activity private int requestCode = -1; //fragment跳转 public CoreSwitchBean(String pageName) { this.mPageName = pageName; } public CoreSwitchBean(String pageName, Bundle bundle) { this.mPageName = pageName; this.mBundle = bundle; } public CoreSwitchBean(String pageName, Bundle bundle, CoreAnim coreAnim) { this.mPageName = pageName; this.mBundle = bundle; this.setAnim(coreAnim); } public void setAnim(CoreAnim anim) { mAnim = convertAnimations(anim); } /** * 动画转化,根据枚举类返回int数组 * * @param coreAnim * @return */ public static int[] convertAnimations(CoreAnim coreAnim) { if (coreAnim == CoreAnim.present) { int[] animations = {R.anim.push_in_down, R.anim.push_no_ani, R.anim.push_no_ani, R.anim.push_out_down}; return animations; } else if (coreAnim == CoreAnim.fade) { int[] animations = {R.anim.alpha_in, R.anim.alpha_out, R.anim.alpha_in, R.anim.alpha_out}; return animations; } else if (coreAnim == CoreAnim.slide) { int[] animations = {R.anim.slide_in_right, R.anim.slide_out_left, R.anim.slide_in_left, R.anim.slide_out_right}; return animations; } return null; } public CoreSwitchBean(String pageName, Bundle bundle, int[] anim) { this.mPageName = pageName; this.mBundle = bundle; this.mAnim = anim; } public CoreSwitchBean(String pageName, Bundle bundle, CoreAnim coreAnim, boolean addToBackStack) { this.mPageName = pageName; this.mBundle = bundle; this.setAnim(coreAnim); this.mAddToBackStack = addToBackStack; } public CoreSwitchBean(String pageName, Bundle bundle, int[] anim, boolean addToBackStack) { this.mPageName = pageName; this.mBundle = bundle; this.mAnim = anim; this.mAddToBackStack = addToBackStack; } public CoreSwitchBean(String pageName, Bundle bundle, CoreAnim coreAnim, boolean addToBackStack, boolean newActivity) { this.mPageName = pageName; this.mBundle = bundle; this.setAnim(coreAnim); this.mAddToBackStack = addToBackStack; this.mNewActivity = newActivity; } public CoreSwitchBean(String pageName, Bundle bundle, int[] anim, boolean addToBackStack, boolean newActivity) { this.mPageName = pageName; this.mBundle = bundle; this.mAnim = anim; this.mAddToBackStack = addToBackStack; this.mNewActivity = newActivity; } public CoreSwitchBean(String pageName, Bundle bundle, int[] anim, boolean addToBackStack, boolean newActivity, int requestCode) { this.mPageName = pageName; this.mBundle = bundle; this.mAnim = anim; this.mAddToBackStack = addToBackStack; this.mNewActivity = newActivity; this.requestCode = requestCode; } protected CoreSwitchBean(Parcel in) { mPageName = in.readString(); mBundle = in.readBundle(); int[] a = {in.readInt(), in.readInt(), in.readInt(), in.readInt()}; mAnim = a; mAddToBackStack = in.readInt() == 1 ? true : false; mNewActivity = in.readInt() == 1 ? true : false; requestCode = in.readInt(); } public String getPageName() { return mPageName; } public void setPageName(String pageName) { mPageName = pageName; } public boolean isNewActivity() { return mNewActivity; } public void setNewActivity(boolean newActivity) { mNewActivity = newActivity; } public boolean isAddToBackStack() { return mAddToBackStack; } public void setAddToBackStack(boolean addToBackStack) { mAddToBackStack = addToBackStack; } public int[] getAnim() { return mAnim; } public void setAnim(int[] anim) { mAnim = anim; } public Bundle getBundle() { return mBundle; } public void setBundle(Bundle bundle) { mBundle = bundle; } public int getRequestCode() { return requestCode; } public void setRequestCode(int requestCode) { this.requestCode = requestCode; } @Override public String toString() { return "SwitchBean{" + "mPageName='" + mPageName + '\'' + ", mBundle=" + mBundle + ", mAnim=" + Arrays.toString(mAnim) + ", mAddToBackStack=" + mAddToBackStack + ", mNewActivity=" + mNewActivity + ", requestCode=" + requestCode + '}'; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel out, int flags) { if (mPageName == null) { mPageName = ""; } if (mBundle == null) { mBundle = new Bundle(); } if (mAnim == null) { int[] a = {-1, -1, -1, -1}; mAnim = a; } out.writeString(mPageName); mBundle.writeToParcel(out, flags); if (mAnim != null && mAnim.length == 4) { out.writeInt(mAnim[0]); out.writeInt(mAnim[1]); out.writeInt(mAnim[2]); out.writeInt(mAnim[3]); } else { out.writeInt(-1); out.writeInt(-1); out.writeInt(-1); out.writeInt(-1); } out.writeInt(mAddToBackStack ? 1 : 0); out.writeInt(mNewActivity ? 1 : 0); out.writeInt(requestCode); } }

该类中的部分属性有一些默认值,比如是否添加到返回栈,是否起新Activity,我们默认在当前activity中打开fragment,并且添加到返回栈。有了这个类,之后的页面切换都通过该实体类进行传参就可以了。

然后,我们定义一个接口,让基类activity实现该接口,用于切换时的一些常用操作。fragment中调用宿主activity中该接口的方法即可。

/** * 页面跳转接口,用于控制页面跳转或启动新的activity */ public interface CoreSwitcher { /** * 返回到前一个页面(只有一个fragment时会关闭Activityt) */ void popPage(); /** * fragmentTag 是否在当前顶上activity上的最顶上的fragment * * @param fragmentTag * @return */ boolean isFragmentTop(String fragmentTag); /** * 是否查找到某个page * * @param pageName * @return */ boolean findPage(final String pageName); /** * 跳转到某一个页面。 * * @param bean * @return */ Fragment gotoPage(CoreSwitchBean bean); /** * 打开一个新的页面 * * @param bean * @return */ Fragment openPage(CoreSwitchBean bean); /** * 移除当前Acitivity不需要的fragment * * @param fragmentLists */ void removeUnlessFragment(List<String> fragmentLists); /** * 页面跳转,支持跨Activity进行传递数据 * * @param page * @param fragment * @return */ public Fragment openPageForResult(final CoreSwitchBean page, final BaseFragment fragment); }

到了这里,似乎已经初具模型了,接下来,我们实现该接口。为了保证在子线程中也能调用这些方法,我们需要一个主线程的handler来帮我们完成一部分工作。假设我们已经获得了这个handler。具体细节看下面的代码实现吧,仔细阅读以下不难理解的。

private static List<WeakReference<BaseActivity>> mActivities = new ArrayList<WeakReference<BaseActivity>>(); //所有activity的引用 private Handler mHandler = null; //线程安全的handler private WeakReference<BaseActivity> mCurrentInstance = null; //当前activity的引用 /** * 弹出页面 */ @Override public void popPage() { popOrFinishActivity(); //如果只有一个Fagment则退出activty } /** * 保证在主线程操作 */ private void popOrFinishActivity() { if (this.isFinishing()) { return; } if (this.getSupportFragmentManager().getBackStackEntryCount() > 1) { if (isMainThread()) { this.getSupportFragmentManager().popBackStackImmediate(); } else { this.mHandler.post(new Runnable() { @Override public void run() { getSupportFragmentManager().popBackStackImmediate(); } }); } } else { finishActivity(this, true); } } /** * 是否是主线程 * @return */ private boolean isMainThread() { return Thread.currentThread() == this.getMainLooper().getThread(); } /** * 是否位于栈顶 * @param fragmentTag * @return */ @Override public boolean isFragmentTop(String fragmentTag) { int size = mActivities.size(); if (size > 0) { WeakReference<BaseActivity> ref = mActivities.get(size - 1); BaseActivity item = ref.get(); if (item != null && item == this) { FragmentActivity activity = item; FragmentManager manager = activity.getSupportFragmentManager(); if (manager != null) { int count = manager.getBackStackEntryCount(); if (count >= 1) { FragmentManager.BackStackEntry entry = manager.getBackStackEntryAt(count - 1); if (entry.getName().equalsIgnoreCase(fragmentTag)) { return true; } } } } } return false; } /** * 查找fragment * @param pageName * @return */ @Override public boolean findPage(String pageName) { int size = mActivities.size(); int j = size - 1; boolean hasFind = false; for (; j >= 0; j--) { WeakReference<BaseActivity> ref = mActivities.get(j); if (ref != null) { BaseActivity item = ref.get(); if (item == null) { Log.d(TAG, "item is null"); continue; } FragmentManager manager = item.getSupportFragmentManager(); int count = manager.getBackStackEntryCount(); for (int i = count - 1; i >= 0; i--) { String name = manager.getBackStackEntryAt(i).getName(); if (name.equalsIgnoreCase(pageName)) { hasFind = true; break; } } if (hasFind) { break; } } } return hasFind; } /** * 弹出并用bundle刷新数据,在onFragmentDataReset中回调 * @param page * @return */ @Override public Fragment gotoPage(CoreSwitchBean page) { if (page == null) { Log.e(TAG, "page name empty"); return null; } String pageName = page.getPageName(); if (!findPage(pageName)) { Log.d(TAG, "Be sure you have the right pageName" + pageName); return this.openPage(page); } int size = mActivities.size(); int i = size - 1; for (; i >= 0; i--) { WeakReference<BaseActivity> ref = mActivities.get(i); if (ref != null) { BaseActivity item = ref.get(); if (item == null) { Log.d(TAG, "item null"); continue; } boolean findInActivity = popFragmentInActivity(pageName, page.getBundle(), item); if (findInActivity) { break; } else { item.finish(); // 找不到就弹出 } } } return null; } /** * 当前activiti中弹fragment * @param pageName * @param bundle * @param findAcitivity * @return */ protected boolean popFragmentInActivity(final String pageName, Bundle bundle, BaseActivity findAcitivity) { if (pageName == null || findAcitivity == null || findAcitivity.isFinishing()) { return false; } else { final FragmentManager fragmentManager = findAcitivity.getSupportFragmentManager(); if (fragmentManager != null) { Fragment frg = fragmentManager.findFragmentByTag(pageName); if (frg != null && frg instanceof BaseFragment) { if (fragmentManager.getBackStackEntryCount() > 1 && mHandler != null) { mHandler.postDelayed(new Runnable() { @Override public void run() { fragmentManager.popBackStack(pageName, 0); } }, 100); } ((BaseFragment) frg).onFragmentDataReset(bundle);//默认为空实现,用于舒心返回时的页面数据,重写改方法完成数据刷新 return true; } } } return false; } /** * 根据Switchpage打开activity * @param page */ public void startActivity(CoreSwitchBean page) { try { Intent intent = new Intent(this, BaseActivity.class); intent.putExtra("SwitchBean", page); this.startActivity(intent); int[] animations = page.getAnim(); if (animations != null && animations.length >= 2) { this.overridePendingTransition(animations[0], animations[1]); } } catch (Exception e) { e.printStackTrace(); Log.e(TAG, e.getMessage()); } } /** * 根据SwitchBean打开fragment * @param page * @return */ @Override public Fragment openPage(CoreSwitchBean page) { boolean addToBackStack = page.isAddToBackStack(); boolean newActivity = page.isNewActivity(); Bundle bundle = page.getBundle(); int[] animations = page.getAnim(); if (newActivity) { startActivity(page); return null; } else { String pageName = page.getPageName(); return CorePageManager.getInstance().openPageWithNewFragmentManager(getSupportFragmentManager(), pageName, bundle, animations, addToBackStack); } } /** * 移除无用fragment * @param fragmentLists */ @Override public void removeUnlessFragment(List<String> fragmentLists) { if (this.isFinishing()) { return; } FragmentManager manager = getSupportFragmentManager(); if (manager != null) { FragmentTransaction transaction = manager.beginTransaction(); for (String tag : fragmentLists) { Fragment fragment = manager.findFragmentByTag(tag); if (fragment != null) { transaction.remove(fragment); } } transaction.commitAllowingStateLoss(); int count = manager.getBackStackEntryCount(); if (count == 0) { this.finish(); } } } /** * 给BaseFragment调用 * @param page * @param fragment * @return */ @Override public Fragment openPageForResult(CoreSwitchBean page, BaseFragment fragment) { if (page != null) { if (page.isNewActivity()) { Log.d(TAG,"openPageForResult start new activity-----"+fragment.getPageName()); mFragmentForResult=fragment; mFragmentRequestCode=page.getRequestCode(); startActivityForResult(page); return null; }else{ String pageName=page.getPageName(); Bundle bundle=page.getBundle(); int[] animations=page.getAnim(); boolean addToBackStack=page.isAddToBackStack(); BaseFragment frg = (BaseFragment) CorePageManager.getInstance().openPageWithNewFragmentManager(getSupportFragmentManager(), pageName, bundle, animations, addToBackStack); if (frg==null){ return null; } final BaseFragment opener= fragment; frg.setRequestCode(page.getRequestCode()); frg.setFragmentFinishListener(new BaseFragment.OnFragmentFinishListener() { @Override public void onFragmentResult(int requestCode, int resultCode, Intent intent) { opener.onFragmentResult(requestCode,resultCode,intent); } }); return frg; } }else{ Log.d(TAG, "openPageForResult.SwitchBean is null"); } return null; } public void startActivityForResult(CoreSwitchBean page) { try { Intent intent = new Intent(this, BaseActivity.class); intent.putExtra("SwitchBean", page); intent.putExtra("startActivityForResult", "true"); this.startActivityForResult(intent, page.getRequestCode()); int[] animations = page.getAnim(); if (animations != null && animations.length >= 2) { this.overridePendingTransition(animations[0], animations[1]); } } catch (Exception e) { e.printStackTrace(); } } /** * 如果是fragment发起的由fragment处理,否则默认处理 * @param requestCode * @param resultCode * @param data */ @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { Log.d(TAG, "onActivityResult from baseActivity" + requestCode + " " + resultCode); if (mFragmentRequestCode == requestCode && mFragmentForResult != null) { mFragmentForResult.onFragmentResult(mFragmentRequestCode, resultCode, data); } super.onActivityResult(requestCode, resultCode, data); }

除此之外,提供一些函数的重载便于调用以及一些工具函数。

/** * 仅用于接受应用退出广播,程序退出时有机会做一些必要的清理工作 */ private BroadcastReceiver mExitReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (action.equals(Config.ACTION_EXIT_APP)) { Log.d(TAG,"exit from broadcast"); finish(); } } }; /** * 返回最上层的activity * * @return */ public static BaseActivity getTopActivity() { if (mActivities != null) { int size = mActivities.size(); if (size >= 1) { WeakReference<BaseActivity> ref = mActivities.get(size - 1); if (ref != null) { return ref.get(); } } } return null; } /** * 广播退出时清理activity列表 */ public static void unInit() { if (mActivities != null) { mActivities.clear(); } } /** * 获得当前活动页面名 * @return */ protected String getPageName() { BaseFragment frg = getActiveFragment(); if (frg != null) { return frg.getPageName(); } return ""; } /** * 打开fragment,并设置是否新开activity,设置是否添加到返回栈 * * @param pageName * @param bundle * @param coreAnim * @param addToBackStack * @param newActivity * @return */ public Fragment openPage(String pageName, Bundle bundle, CoreAnim coreAnim, boolean addToBackStack, boolean newActivity) { CoreSwitchBean page = new CoreSwitchBean(pageName, bundle, coreAnim, addToBackStack, newActivity); return openPage(page); } /** * 打开fragment,并设置是否新开activity,设置是否添加到返回栈 * * @param pageName * @param bundle * @param anim * @param addToBackStack * @param newActivity * @return */ public Fragment openPage(String pageName, Bundle bundle, int[] anim, boolean addToBackStack, boolean newActivity) { CoreSwitchBean page = new CoreSwitchBean(pageName, bundle, anim, addToBackStack, newActivity); return openPage(page); } /** * 打开fragment,并设置是否添加到返回栈 * * @param pageName * @param bundle * @param coreAnim * @param addToBackStack * @return */ public Fragment openPage(String pageName, Bundle bundle, CoreAnim coreAnim, boolean addToBackStack) { CoreSwitchBean page = new CoreSwitchBean(pageName, bundle, coreAnim, addToBackStack); return openPage(page); } /** * 打开fragment,并设置是否添加到返回栈 * * @param pageName * @param bundle * @param anim * @param addToBackStack * @return */ public Fragment openPage(String pageName, Bundle bundle, int[] anim, boolean addToBackStack) { CoreSwitchBean page = new CoreSwitchBean(pageName, bundle, anim, addToBackStack); return openPage(page); } /** * 打开fragment * * @param pageName * @param bundle * @param coreAnim * @return */ public Fragment openPage(String pageName, Bundle bundle, CoreAnim coreAnim) { CoreSwitchBean page = new CoreSwitchBean(pageName, bundle, coreAnim); return openPage(page); } /** * 打开fragment * * @param pageName * @param bundle

时间: 2024-10-07 07:57:35

Android使用Fragment打造万能页面切换框架的相关文章

Android使用Fragment打造万能页面切换框架_Android

首先我们来回忆一下传统用Activity进行的页面切换,activity之间切换,首先需要新建intent对象,给该对象设置一些必须的参数,然后调用startActivity方法进行页面跳转.如果需要activity返回结果,则调用startActivityForResult方法,在onActivityResult方法中获得返回结果.此外,每一个要展示的activity需要在AndroidManifest.xml文件中注册.而且,如果在某些特定的情况下(比如65536方法数爆炸)要动态加载dex

Android Fragment中使用SurfaceView切换时闪一下黑屏的解决办法_Android

重构了下之前自己的一个新闻客户端,全部使用了Fragment来进行页面切换,只有一个入口Activity作为程序的启动Activity,其中有一个界面需要调用摄像头识别二维码,于是就会用到SurfaceView进行预览,那么问题来了,当切换到对应的Fragment时,屏幕会黑一下,黑了1秒左右就显示出正常的界面,而且这种现象只有第一次进入该Fragment才会出现,之后进入都不会出现,解决方法是无意在github上看到了,试了一下,可以行的通,下面贴出解决方法. 方法一.在Activity的on

Android Fragment中使用SurfaceView切换时闪一下黑屏的解决办法

重构了下之前自己的一个新闻客户端,全部使用了Fragment来进行页面切换,只有一个入口Activity作为程序的启动Activity,其中有一个界面需要调用摄像头识别二维码,于是就会用到SurfaceView进行预览,那么问题来了,当切换到对应的Fragment时,屏幕会黑一下,黑了1秒左右就显示出正常的界面,而且这种现象只有第一次进入该Fragment才会出现,之后进入都不会出现,解决方法是无意在github上看到了,试了一下,可以行的通,下面贴出解决方法. 方法一.在Activity的on

Android中使用TabHost 与 Fragment 制作页面切换效果_Android

三个标签页置于顶端 效果图: 在文件BoardTabHost.java中定义页面切换的效果:切换页面时,当前页面滑出,目标页面滑入.这是2个不同的动画设定动画时要区分对待 import android.content.Context; import android.util.AttributeSet; import android.view.animation.Animation; import android.view.animation.TranslateAnimation; import

Android中使用TabHost 与 Fragment 制作页面切换效果

三个标签页置于顶端 效果图: 在文件BoardTabHost.java中定义页面切换的效果:切换页面时,当前页面滑出,目标页面滑入.这是2个不同的动画设定动画时要区分对待 import android.content.Context; import android.util.AttributeSet; import android.view.animation.Animation; import android.view.animation.TranslateAnimation; import

Android使用TabLayou+fragment+viewpager实现滑动切换页面效果

TabLayou 主要实现的是标题头的 滑动 这个 控件 类似于 ScrollView XML中的布局 <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <android.support.design.widget.TabLayout a

Android 使用Vitamio打造自己的万能播放器(6)——在线播放(播放列表)_Android

前言 新版本的VPlayer由设计转入开发阶段,预计开发周期为一个月,这也意味着新版本的Vitamio将随之发布,开发者们可以和本系列文章一样,先开发其他功能.本章内容为"在线视频播放列表",集合了主流各大视频网站的手机版,欢迎给"开播视频"反馈改进建议! 系列 1.Android 使用Vitamio打造自己的万能播放器(1)--准备 2.Android 使用Vitamio打造自己的Android万能播放器(2)-- 手势控制亮度.音量.缩放 3.Android 使

Android 使用Vitamio打造自己的万能播放器(3)——本地播放(主界面、播放列表)_Android

前言  打造一款完整可用的Android播放器有许多功能和细节需要完成,也涉及到各种丰富的知识和内容,本章将结合Fragment.ViewPager来搭建播放器的主界面,并实现本地播放基本功能.系列文章提供截图.代码说明.源码下载,欢迎交流! 系列  1.Android 使用Vitamio打造自己的万能播放器(1)--准备  2.Android 使用Vitamio打造自己的万能播放器(2)-- 手势控制亮度.音量.缩放  正文  一.目标   1.1 使用Fragment.ViewPager搭建

android中fragment切换的时候遇到非法参数异常的问题

问题描述 android中fragment切换的时候遇到非法参数异常的问题 FragmentTabHost中加入了两个fragment,片段1和片段2.运行后从片段1到片段2没有出问题,当从片段2再回到片段1的时候出现非法参数异常. 异常代码如图 找到错误主要原因是设置了一个布局的ID: gridview_layout.setId(CAL_LAYOUT_ID); 请问为什么在fragment中设置id切换片段后会发生异常? 解决方案 你这两个页面的id相同 但是页面不是同一种类型的 有冲突