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.png1、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是同一个对象。
网友评论