Fragment版本变化2

2022-08-09  本文已影响0人  freelifes

Fragment数据通信

1、ViewModel

Fragment 及其宿主 Activity 均可通过将 Activity 传入 [ViewModelProvider]构造函数来使用 Activity 范围检索 ViewModel的共享实例,Fragment和Activity都可以观察和修改ViewModel中的数据。
1、Activity与Fragment之间共享数据。

//Activity
 private val viewModel: ItemViewModel by viewModels()
//Fragment:
 private val viewModel: ItemViewModel by activityViewModels()

2、Fragment之间共享数据,Activity也作为共享范围。
3、父Fragment和子Fragment之间共享数据,将把父Fragment的Viewmodel作为参考范围。

//ChildFragment
val viewModel: ListViewModel by viewModels({requireParentFragment()})
2、使用 Fragment Result API
B发送数据给A.png

1、B Fragment通过key发送数据给FragmentManager。
2、如果A Fragment处于Started之后,直接回调listener.onFragmentResult()。
3、A Fragment如果处于Started状态之前。将mResults存在map
中,监听生命周期处于Started之后,再回调listener。观察者模式 + lifecycler。

@Test
fun testFragmentResultListener() {
    val scenario = launchFragmentInContainer<ResultListenerFragment>()
    scenario.onFragment { fragment ->
        val expectedResult = "result"
        fragment.parentFragmentManager.setFragmentResult("requestKey", bundleOf("bundleKey" to expectedResult))
        assertThat(fragment.result).isEqualTo(expectedResult)
    }
}

class ResultListenerFragment : Fragment() {
    var result : String? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // Use the Kotlin extension in the fragment-ktx artifact
        setFragmentResultListener("requestKey") { requestKey, bundle ->
            result = bundle.getString("bundleKey")
        }
    }
}

上面这段代码必须是同一个manager,在Activity中也能拿到,因为最上层supportmanager和fragmentmanager是同一个对象。
如果是子Fragment注册表是在子FragmentManager中,因此在父Fragment中使用,要找到子Frgament,拿到它的manager才能使用。

//父Fragment
childFragmentManager.setFragmentResultListener("requestKey") { key, bundle ->
        val result = bundle.getString("bundleKey")
        // Do something with the result
    }
// 子Fragment
button.setOnClickListener {
    val result = "result"
    // Use the Kotlin extension in the fragment-ktx artifact
    setFragmentResult("requestKey", bundleOf("bundleKey" to result))
}

上段代码中的childFragmentManager 可以写成childFragment.parentFragment.setxxxListener。只要保证存和取在同一个Manager中就可以了。

ViewPage2

ViewPager2优点:

1、支持 水平、垂直方向滚动。

如下,内部实现通过创建一个mRecyclerView,支持水平和垂直方向的滚动。每一个page就是一个RecyclerView的item。

public final class ViewPager2 extends ViewGroup {

 private void initialize(Context context, AttributeSet attrs) {

        mRecyclerView = new ViewPager2.RecyclerViewImpl(context);
        mRecyclerView.setId(ViewCompat.generateViewId());
        mRecyclerView.setDescendantFocusability(FOCUS_BEFORE_DESCENDANTS);

        mLayoutManager = new ViewPager2.LinearLayoutManagerImpl(context);
        mRecyclerView.setLayoutManager(mLayoutManager);
        mRecyclerView.setScrollingTouchSlop(RecyclerView.TOUCH_SLOP_PAGING);
        setOrientation(context, attrs);

        mRecyclerView.setLayoutParams(
                new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
        mRecyclerView.addOnChildAttachStateChangeListener(enforceChildFillListener());

        // Create ScrollEventAdapter before attaching PagerSnapHelper to RecyclerView, because the
        // attach process calls PagerSnapHelperImpl.findSnapView, which uses the mScrollEventAdapter
        mScrollEventAdapter = new ScrollEventAdapter(this);
        // Create FakeDrag before attaching PagerSnapHelper, same reason as above
        mFakeDragger = new FakeDrag(this, mScrollEventAdapter, mRecyclerView);
        mPagerSnapHelper = new ViewPager2.PagerSnapHelperImpl();
        mPagerSnapHelper.attachToRecyclerView(mRecyclerView);
        // Add mScrollEventAdapter after attaching mPagerSnapHelper to mRecyclerView, because we
        // don't want to respond on the events sent out during the attach process
        mRecyclerView.addOnScrollListener(mScrollEventAdapter);

        mPageChangeEventDispatcher = new CompositeOnPageChangeCallback(3);
        mScrollEventAdapter.setOnPageChangeCallback(mPageChangeEventDispatcher);

        attachViewToParent(mRecyclerView, 0, mRecyclerView.getLayoutParams());
    }
2、支持懒加载

由recyclerView加载item可知,从缓存中寻找Item或者创建,然后就bindView,此时view还没有加入recyclerView中,预加载的view存在CacheViews[]中,也没有加入到RecyclerView。
2.1、针对RecyclerView.Adapter,Item是View,创建View,仅仅绑定了数据。
2.2、针对fragmentStateAdapter,item是fragment, 仅仅创建Fragment对象,没有调用Fragment的生命周期。

3、复用

使用fragmentStateAdapter代替fragmentPagerAdapter 和 fragmentStatePagerAdapter,更好的复用性。
3.1、fragmentStatePagerAdapter特点 :

 public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
        Fragment fragment = (Fragment) object;
        mFragments.set(position, null);
        mSavedState.set(position, fragment.isAdded()
                ? mFragmentManager.saveFragmentInstanceState(fragment) : null);
        mFragments.set(position, null);
        mCurTransaction.remove(fragment);
        if (fragment == mCurrentPrimaryItem) {
            mCurrentPrimaryItem = null;
        }
    }

 @Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
        Fragment fragment = getItem(position);
       if (mSavedState.size() > position) {
            Fragment.SavedState fss = mSavedState.get(position);
            if (fss != null) {
                fragment.setInitialSavedState(fss);
            }
        }
        mFragments.set(position, fragment);
        mCurTransaction.add(container.getId(), fragment);
        return fragment;
    }

可以看到StatePagerAdapte在销毁page的时候,存储state(包括view的state,自定义的数据)等,接着remove,创建page的时先是创建fragment,然后恢复state,接着add。
3.2、fragmentPagerAdapter特点:

public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
        Fragment fragment = (Fragment) object;
        mCurTransaction.detach(fragment);
    }
  public Object instantiateItem(@NonNull ViewGroup container, int position) {
        final long itemId = getItemId(position);
        String name = makeFragmentName(container.getId(), itemId);
        Fragment fragment = mFragmentManager.findFragmentByTag(name);
        if (fragment != null) {
            mCurTransaction.attach(fragment);
        } else {
            fragment = getItem(position);
            mCurTransaction.add(container.getId(), fragment,
                    makeFragmentName(container.getId(), itemId));
        }
        return fragment;
    }

可以看到fragmentPagerAdapter销毁page的时候,fragment只是detach,创建page时,如果存在则findFragmentByTag找到之前的复用,如果不存在则创建新的fragment。
3.3、比较上面2者:
fragmentStatePagerAdapter适合fragment页面多,缓存少,缓存了左右2个fragment,page销毁时移除fragment但缓存了state。
fragmentPagerAdapter缓存了所有的Fragment实例,适合页面少的场景。
3.4、viewPager2的FragmentStateAdapter
同样看下创建和销毁

private void ensureFragment(int position) {
        long itemId = getItemId(position);
        if (!mFragments.containsKey(itemId)) {
            Fragment newFragment = createFragment(position);
            newFragment.setInitialSavedState(mSavedStates.get(itemId));
            mFragments.put(itemId, newFragment);
        }
    }
 @Override
    public final void onViewRecycled(@NonNull FragmentViewHolder holder) {
        final int viewHolderId = holder.getContainer().getId();
        final Long boundItemId = itemForViewHolder(viewHolderId); // item currently bound to the VH
        if (boundItemId != null) {
            removeFragment(boundItemId);
            mItemIdToViewHolder.remove(boundItemId);
        }
    }
 private void removeFragment(long itemId) {
        Fragment fragment = mFragments.get(itemId);
        if (fragment.getView() != null) {
            ViewParent viewParent = fragment.getView().getParent();
            if (viewParent != null) {
                ((FrameLayout) viewParent).removeAllViews();
            }
        }
        if (fragment.isAdded() && containsItem(itemId)) {
            mSavedStates.put(itemId, mFragmentManager.saveFragmentInstanceState(fragment));
        }
        mFragmentManager.beginTransaction().remove(fragment).commitNow();
        mFragments.remove(itemId);
    }

onViewRecycled方法是把ViewHolder放入RecyclerPool中时调用的,放入 pool中的viewholder会清除数据和重置Flag等。
可以看到onViewRecycled方法回收fragment时,移除了fragment,保存fragment的state。当创建fragment时候,先从recyclerView的四级缓存中获取,直至创建fragment, 保存的state为了恢复fragment。ViewPager2没有保存fragment的实例,但是我们知道recyclerview缓存原理,以LinearLayoutManager为例,此时mCachedViews中保存了2个实例对象,和一个预加载的实例对象。
3.5、综上ViewPager2.fragmentStateAdapter的优点
1)、保留了fragmentStatePagerAdapter的优点,例如: 保存state,占用内存少的优点。
2)、如果设置mCachedViews缓存个数,就和fragmentPagerAdapter相似,可以保留所有对象实例,不需要再次创建。
3)、其他优点,由于recyclerView的4级缓存,可以从缓存池Pool中拿到viewholder对象(fragment),避免创建,只需要绑定数据。

4、支持fake drag

在其他View上滑动,将滑动事件作用于ViewPager2。如下使用,拦截事件,将事件交给ViewPager处理。

  findViewById<View>(R.id.touchpad).setOnTouchListener { _, event ->
            handleOnTouchEvent(event)
  }

private fun handleOnTouchEvent(event: MotionEvent): Boolean {
        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                lastValue = getValue(event)
                viewPager.beginFakeDrag()
            }

            MotionEvent.ACTION_MOVE -> {
                val value = getValue(event)
                val delta = value - lastValue
                viewPager.fakeDragBy(if (viewPager.isHorizontal) mirrorInRtl(delta) else delta)
                lastValue = value
            }

            MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> {
                viewPager.endFakeDrag()
            }
        }
        return true
    }

4.1、beginFakeDrag : 开始一个假的拖拽,初始化拖拽速度跟踪器。
4.2、fakeDragBy(@Px float offsetPxFloat) :手指拖动距离作用于recyclerView,recyclerView开始滚动,mVelocityTracker.addMovement(ev);

  @UiThread
    boolean fakeDragBy(float offsetPxFloat) {
        final int offsetX = isHorizontal ? offsetPx : 0;
        final int offsetY = isHorizontal ? 0 : offsetPx;
        // Motion events get the raw float distance:
        final float x = isHorizontal ? mRequestedDragDistance : 0;
        final float y = isHorizontal ? 0 : mRequestedDragDistance;

        mRecyclerView.scrollBy(offsetX, offsetY);
        addFakeMotionEvent(time, MotionEvent.ACTION_MOVE, x, y);
        return true;
    }

4.3、endFakeDrag: 结束假的拖拽。速度追踪,计算recylerView的fling。recyelerView的Fling结束后,再调用snapToPage,将最接近屏幕中间item滚动到屏幕中间。

boolean endFakeDrag() {
        final int pixelsPerSecond = 1000;
        final VelocityTracker velocityTracker = mVelocityTracker;
        velocityTracker.computeCurrentVelocity(pixelsPerSecond, mMaximumVelocity);
        int xVelocity = (int) velocityTracker.getXVelocity();
        int yVelocity = (int) velocityTracker.getYVelocity();
        // And fling or snap the ViewPager2 to its destination
        if (!mRecyclerView.fling(xVelocity, yVelocity)) {
            // Velocity too low, trigger snap to page manually
            mViewPager.snapToPage();
        }
        return true;
    }
生命周期状态
 public enum State {
       
        DESTROYED, 
        INITIALIZED,
        CREATED,
        STARTED,
        RESUMED;

        public boolean isAtLeast(@NonNull State state) {
            return compareTo(state) >= 0;
        }
    }

1)、DESTROYED ----> Activity 的Destroy之前回调。
2)、INITIALIZED -----> Activity的构造函数中调用,onCreate之前。
3)、CREATED -------> Activity的 onCreate之后调用
-------->或者onStop之前调用
4)、STARTED -------> Activity的onStart之后调用
------->Activity的onPause之前调用
5)、RESUMED ------->Activity的onResume之后调用

    public FragmentTransaction setMaxLifecycle(@NonNull Fragment fragment,
            @NonNull Lifecycle.State state) {
        addOp(new Op(OP_SET_MAX_LIFECYCLE, fragment, state));
        return this;
    }

这个方法的目的是给fragment设置一个上限的状态。如果这个fragment的状态大于了这个上限状态,将down to 这个状态。
如果fragment已经超过INITIALIZED状态,再次设置这个INITIALIZED将会报错。
不可以给fragment设置DESTROYED状态,将会抛出IllegalArgumentException。

懒加载使用
----onCreate()
  beginTransaction
                .setMaxLifecycle(fragmentOne!!, Lifecycle.State.STARTED)
                .setMaxLifecycle(fragmentTwo!!, Lifecycle.State.INITIALIZED)
                .setMaxLifecycle(fragmentThree!!, Lifecycle.State.INITIALIZED)
        beginTransaction.show(fragmentOne!!).hide(fragmentTwo!!).hide(fragmentThree!!)
                .commitAllowingStateLoss()

----onClick()
R.id.btn_lazy_load_2 -> {
                var beginTransaction = this@FragmentContainerActivity.supportFragmentManager.beginTransaction()
                Log.d(TAG, "onClick: " + hasResume(fragmentTwo!!))
                if (!hasResume(fragmentTwo!!)) {
                    beginTransaction.setMaxLifecycle(fragmentTwo!!, Lifecycle.State.RESUMED)
                }
                beginTransaction
                        .hide(fragmentThree!!).hide(fragmentOne!!).show(fragmentTwo!!)
                        .commitAllowingStateLoss()
            }
函数解释

1、setOffscreenPageLimit 设置每一边可以保留的page. 当超过这个limit的时候,会被recyclerView回收,下次从回收策略中获取。在limit范围内的就被加入到view层级,即使这些page不可见。如果复杂的page,应该尽可能保持limit的值小。
例如: limit = 2 则进入页面,会创建 0 -1 - 2 初始化生命周期,复杂的页面会导致加载耗时,页面卡顿。想利用缓存,我们可以设置recyclerView的离屏缓存cache达到缓存的目的,又不会破坏懒加载,如下。

  var recyclerView1 = viewPager.getChildAt(0) as RecyclerView
  recyclerView1.setItemViewCacheSize(2)

2、ScrollEventAdapter 将RecyclerView.OnScrollListener的事件转化成OnPageChangeCallback

 public abstract static class OnPageChangeCallback {
        /**
         * This method will be invoked when the current page is scrolled, either as part
         * of a programmatically initiated smooth scroll or a user initiated touch scroll.
         *
         * @param position Position index of the first page currently being displayed.
         *                 Page position+1 will be visible if positionOffset is nonzero.
         * @param positionOffset Value from [0, 1) indicating the offset from the page at position.
         * @param positionOffsetPixels Value in pixels indicating the offset from position.
         */
        public void onPageScrolled(int position, float positionOffset,
                @Px int positionOffsetPixels) {
        }

        /**
         * This method will be invoked when a new page becomes selected. Animation is not
         * necessarily complete.
         *
         * @param position Position index of the new selected page.
         */
        public void onPageSelected(int position) {
        }
    


        /**
         * Called when the scroll state changes. Useful for discovering when the user begins
         * dragging, when a fake drag is started, when the pager is automatically settling to the
         * current page, or when it is fully stopped/idle. {@code state} can be one of {@link
         * #SCROLL_STATE_IDLE}, {@link #SCROLL_STATE_DRAGGING} or {@link #SCROLL_STATE_SETTLING}.
         */
        public void onPageScrollStateChanged(@ScrollState int state) {
        }
    }

postion : 位置
positionOffset : [0,1) 位置偏移量
positionOffsetPixels : 偏移的像素值。比如手指向上滚动,这个值为负值。
onPageScrolled(int position, float positionOffset,@Px int positionOffsetPixels)

3、isStateSaved() : 如果Activity在调用onSaveInstanceState,也就fragment的onFragmentSaveInstanceState之后,fragment状态已经保存了,此时在beginTranaction,执行事务,改变fragment的状态,会得到错误,因此使用commitAllowingStateLoss代替commit()。

4、getSupportFragmentManager()Activity管理Fragment的manager
getParentFragmentManager() 返回管理当前Fragment的manager。
getChildFragmentManager() 返回管理子Fragment中的manager。
如果A Fragment在MainActivity中,则A Fragment调用getParentFragmentManager 和 MainActivity中返回getSupportFragmentManager是同一个对象。

上一篇 下一篇

猜你喜欢

热点阅读