Android ViewPager在Activity中获取到当前正在执行的Fragment

背景

本来想着是,学了大概一年的Android,也一直是在实验室帮忙修修项目改改BUG,要不就是平常跟着 《第一行代码》《Android编程权威指南》 等参考资料做做小Demo,总的来说,知识都比较碎,而且主观的兴趣没有被积极调动起来。于是想着,自己单独做一个简单点的小应用——平时上课记记简单的便签、存一下签到网址、校园网的登录网址等等,方便生活的同时也是对自己能力的巩固和锻炼吧。这样才能发现之前学习中的漏洞,有很多东西不是想象中的那么简单。

出现问题

做到这一步——为活动设置一个全局的FloatingActiondianjiButton,想根据当前加载的不同Fragment响应不同事件。这就需要在点击事件中进行判断,当前是哪一个Fragment。应用主页面截图
但是在Activity中获取到当前的Fragment实例成了个难题。

尝试解决:使用FragmentManager.findFragmentById()但出现问题

最开始想在Activity中,看看FragmentManager这个容器有没有什么好方法。
也查询了一些资料找到了findFragmentById()这哥。这个方法传入的是Fragment显示的容器id,我这里是R.id.myPager(布局组件ViewPager)。通过显示Fragment的容器来获取到当前Fragment的实例。
我这里一共三个Fragment。在点击事件中使用了该方法后,经过调试发现,不论我翻到哪一个Fragment,FragmentManager.findFragmentById()返回的都是按顺序最后一个Fragment。这就很奇怪。

从源码查找原因

于是我查了的源码:

@Nullable
    public Fragment findFragmentById(int id) {
        int i;
        Fragment f;
        for(i = this.mAdded.size() - 1; i >= 0; --i) {
            f = (Fragment)this.mAdded.get(i);
            if (f != null && f.mFragmentId == id) {
                return f;
            }
        }

        if (this.mActive != null) {
            for(i = this.mActive.size() - 1; i >= 0; --i) {
                f = (Fragment)this.mActive.valueAt(i);
                if (f != null && f.mFragmentId == id) {
                    return f;
                }
            }
        }
        return null;
    }

可以看出,该方法的返回值都来源于两个for循环中。其中的mAdded和mActive使用ctrl+左键查看可以看到,

final ArrayList<Fragment> mAdded = new ArrayList();
    SparseArray<Fragment> mActive;

它们都是Fragment泛型的。大概就是所有已经添加的或者是所有在执行的Fragment的组合(个人理解)。
这时再细看这两个for循环的条件:

 for(i = this.mAdded.size() - 1; i >= 0; --i) 
for(i = this.mActive.size() - 1; i >= 0; --i)

都是在Array中从前往后找的——从最大下标一直到0。

再看在for中的内含逻辑(两个for中的内含逻辑差不多,在此就分析第一个for的逻辑):

		int i;
        Fragment f;
        for(i = this.mAdded.size() - 1; i >= 0; --i) {
            f = (Fragment)this.mAdded.get(i);
            if (f != null && f.mFragmentId == id) {
                return f;
            }
        }

由上,首先新建计数器i,紧接着新建一个Fragment对象用于for中代码f = (Fragment)this.mAdded.get(i);赋值——获取到列表mAdded中的当前Fragment对象。紧接着判断,if (f != null && f.mFragmentId == id),如果该对象不为空且其f.mFragmentId(应该指的是存放该Fragment的容器id)和id(这个id是从findFragmentById(int id)中传过来的参数id)。就这样从size()-1一直到0依次判断id值是否相等。
那么就事论事来说,我创建的几个Fragment,用的都是同一个容器——R.id.myPaper,按照源码中这个意思,如果我把R.id.myPaper当作参数传给findFragmentById(int id),这三个Fragment无论是谁都可以匹配成功。当然由于findFragmentById(int id)中是在存储Fragment的列表中从后往前找的,所以对于这三个Fragment,最后一个被添加的,也就是在列表的最末端的,上来就中招——符合f != null && f.mFragmentId == id,并得到返回return f;。因此不论切换到哪个Fragment,通过FragmentManager.findFragmentById()获得的Fragment实例都是活动中按顺序添加的最后一个Fragment的实例。
至于网上也有一些教程就是这么做的,可能是因为我用了ViewPager的原因?至少现在在我这儿这个方法行不通。

解决方案

我们要相信Android为我们封装的Adapter功能的强大。
何必在Activity来回中折腾呢?这种逐个获得Item实例的事情就是dapter最擅长的。我们完全可以在Adapter中找到合适的方法获取我们想要的实例。
设置的ViewPagerAdapter继承的是 FragmentStatePagerAdapter(Fragment切换之后,它会销毁之前的Fragment,再切换回来时,显示的Fragment是新建的;区别于FragmentPagerAdapter,切换Fragment时,它只是销毁了该Fragment的视图,但是实例还在FragmentManager中。)
在FragmentStatePagerAdapter中,有一个可重写方法 setPrimaryItem(),这个方法是干什么的?到Android Developer的官网查了一下对其的解释:

 * Called to inform the adapter of
 * which item is currently considered to be the "primary",
 * that is the one show to the user as the current page.

大致意思就是,该方法告诉适配器哪一个页当前是重要的——对用户来说就是当前页(current page)。简单来说,就是拿到了当前页。 没错,正式我们要找的。
该方法的参数有:

public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object)

( @NonNull在这里表示该参数不可为null)
其中object传来的就是当前执行的Fragment的一个实例,完全可以写出如下的判断逻辑:

if(object instanceof SchoolPage)
{
	// Someting to do
}

来判断当前加载的是不是我们想要的实例。

实际操作

对我我这个项目,由于点击按钮是全局的,所以点击事件还是放在MainActivity中。当前执行的Fragment实例,在adapter中的setPrimaryItem()获取到之后,再传递给MainActiviy。(这里为了避免AndroidStudio报警告,我就在Adapter中为获取当前Fragment实例的全局变量有设置了一个get方法,在MainActivity中调用,而没有使用static变量。)
(在Activity中都是一般的设置,就不上代码了,以下是 ViewPagerAdapter中的操作):

package com.example.myschool.adapters;

import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.view.ViewGroup;

import java.util.List;
/**
 * @author PRETA
 * 为了获取当前执行的Fragment实例,特别在该适配器中的setPrimaryItem方法为instantFragment赋值,
 * 但是不知道为什么通过getItemPosition中赋值就不行——instantFragment在该方法中复制后在调试的时候就一直报空;
 * 不过也有可能是适配器中根本就还没有进行到该方法的执行。getItemPosition官方文档给的解释是——
 * 该方法用于查看item的位置是否发生改变。其实这样看来——这些复写的方法中都传过来了很多对象值,真的可以好好地利用一下,
 * 何必一根筋地在MainActivity为了获取某些值而反复折腾呢?——其实在Adapter中都帮我们一五一十地设计好了。
 *
 * 而且再看setPrimaryItem的官方文档介绍:Called to inform the adapter of
 * which item is currently considered to be the "primary",
 * that is the one show to the user as the current page.
 * ——告诉适配器哪一个页是重要的——对用户来说就是当前页(current page)。简单来说,就是拿到了当前页。
 * 因此在该方法中获取当前页再合适不过了。再查看super.setPrimaryItem(container, position, object)中的大致逻辑也是如此——获取当前页。
 * */
public class ViewPagerAdapter extends FragmentStatePagerAdapter{

    private Context mContext;
    private FragmentManager manager;
    public static Fragment instantFragment;
    //private Fragment replaceFragment;
    private List<Fragment> tabFragments;
    private List<String> tabTitles;
    // private int mPosition;
    // private boolean canReplace;

    public ViewPagerAdapter(Context context, FragmentManager fm, List<Fragment> fragments,
                            List<String> titles) {
        super(fm);
        manager = fm;
        mContext = context;
        tabFragments = fragments;
        tabTitles = titles;
    }
    
    @Override
    public int getItemPosition(@NonNull Object object) {
        // 可以即时刷新Fragment
        return POSITION_NONE;
    }

    @Override
    public Fragment getItem(int position) {
        return tabFragments.get(position);
    }

    @Override
    public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
        instantFragment = (Fragment) object;
        super.setPrimaryItem(container, position, object);
    }

    public Fragment getInstantFragment(){
        return instantFragment;
    }

    @Override
    public int getCount() {
        return tabFragments.size();
    }

    @Nullable
    @Override
    public CharSequence getPageTitle(int position) {
        return tabTitles.get(position);
    }
}

在MainActivity中如下调用即可:

 ViewPagerAdapter adapter = new  ViewPagerAdapter(//省略参数);
 ......
 /**浮动按钮点击事件*/
    @Override
    public void onClick(View v) {
        
        if(adapter.getInstantFragment() instanceof SchoolPage){
            showToast(mContext, "这是学校页面");
        }
      
 	 ......
	 }

这样设置后,点击事件响应正确。

  • 7
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值