[Android Framework Series] Capítulo 15 Princípios relacionados de Fragment + ViewPager e Viewpager2

1. Introdução

No capítulo anterior [Android Framework Series], Capítulo 14 Princípios Básicos do Fragmento (versão AndroidX), aprendemos os princípios básicos do Fragment. Neste capítulo, aprendemos os usos comumente usados Fragment+ViewPager​​e Fragment+ViewPager2relacionados e algumas análises básicas do código-fonte.

Insira a descrição da imagem aqui

2 Fragmento + ViewPager

Normalmente usamos duas PagerAdapterclasses de implementação, a saber , FragmentStatePagerAdaptere FragmentPagerAdapter. Hoje aprenderemos como usá-las e compará-las.

2.1 A diferença entre FragmentPagerAdapter e FragmentStatePagerAdapter

1. fragmentsProcessamento de objetos::
FragmentPagerAdapterFora do escopo fragmentsserá salvo na memória ( detach), mas fragmento objeto correspondente Viewserá destruído
FragmentStatePagerAdapter: Fora do escopo fragmentsnão será salvo na memória ( remove), Viewe também será destruído.
2. Processamento de status:: Os correspondentes
FragmentPagerAdapterfora do intervalo serão salvos : Apenas os correspondentes dentro do intervalo serão salvos . Isso é usado para parâmetros externos a serem passados ​​no retorno de chamada do ciclo de vida e é semelhante. 3. Cenários aplicáveis: A mesma quantidade , a memória é maior, mas a troca de página é mais amigável, o uso de memória é pequeno, a troca de página é um pouco pior.fragmentsSavedState
FragmentStatePagerAdapterfragmentsSavedStateSavedStateFragmentActivity
fragmentsFragmentPagerAdapterFragmentStatePagerAdapter

Portanto, FragmentPagerAdapteré adequado para Fragmentsituações onde o número é pequeno e FragmentStatePagerAdapteré adequado para situações onde o número de fragmentos é grande.

Vamos dar uma olhada primeiro FragmentPagerAdapter:

	@Override
    public Object instantiateItem(ViewGroup container, int position) {
    
    
        if (mCurTransaction == null) {
    
    
            mCurTransaction = mFragmentManager.beginTransaction();
        }

        final long itemId = getItemId(position);

        // Do we already have this fragment?
        //判断请求的Fragment是否已经被生成过
        String name = makeFragmentName(container.getId(), itemId);
        Fragment fragment = mFragmentManager.findFragmentByTag(name);
        if (fragment != null) {
    
    
            if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
            // 当前缓存有则直接使用
            mCurTransaction.attach(fragment);
        } else {
    
    
            fragment = getItem(position); 
            //调用这个方法来生成新的Fragment
            if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
            // 将新生成的Fragment存储起来,以便以后再次用到时,直接attach()
            mCurTransaction.add(container.getId(), fragment,
                    makeFragmentName(container.getId(), itemId)); 
        }
   		if (fragment != mCurrentPrimaryItem) {
    
    
            fragment.setMenuVisibility(false);
            if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
    
    
                mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);
            } else {
    
    
                fragment.setUserVisibleHint(false);
            }
        }

        return fragment;
    }
    
    @Override
    public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
    
    
        Fragment fragment = (Fragment) object;

        if (mCurTransaction == null) {
    
    
            mCurTransaction = mFragmentManager.beginTransaction();
        }
        if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object
                + " v=" + fragment.getView());
        mCurTransaction.detach(fragment);
        if (fragment.equals(mCurrentPrimaryItem)) {
    
    
            mCurrentPrimaryItem = null;
        }
    }

FragmentPagerAdapterO destroyItemmétodo chamado detach()altera apenas o estado do Fragment, o que significa que somente eliminando o adaptertempo inteiro é que todos os gerados podem Fragmentser eliminados, caso contrário ficarão diretamente na memória.

A seguir comparamos FragmentStatePagerAdapter:

  @Override
    public Object instantiateItem(@NonNull ViewGroup container, int position) {
    
    
        // If we already have this item instantiated, there is nothing
        // to do.  This can happen when we are restoring the entire pager
        // from its saved state, where the fragment manager has already
        // taken care of restoring the fragments we previously had instantiated.
        // mFragments中对应位置有Fragment的情况下直接返回
        if (mFragments.size() > position) {
    
    
            Fragment f = mFragments.get(position);
            if (f != null) {
    
    
                return f;
            }
        }

        if (mCurTransaction == null) {
    
    
            mCurTransaction = mFragmentManager.beginTransaction();
        }

        Fragment fragment = getItem(position);
        if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
        if (mSavedState.size() > position) {
    
    
            Fragment.SavedState fss = mSavedState.get(position);
            if (fss != null) {
    
    
                fragment.setInitialSavedState(fss);
            }
        }
        while (mFragments.size() <= position) {
    
    
            mFragments.add(null);
        }
        fragment.setMenuVisibility(false);
        if (mBehavior == BEHAVIOR_SET_USER_VISIBLE_HINT) {
    
    
            fragment.setUserVisibleHint(false);
        }

        mFragments.set(position, fragment);
        mCurTransaction.add(container.getId(), fragment);

        if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
    
    
            mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);
        }

        return fragment;
    }
    
  	@Override
    public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
    
    
        Fragment fragment = (Fragment) object;

        if (mCurTransaction == null) {
    
    
            mCurTransaction = mFragmentManager.beginTransaction();
        }
        if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object
                + " v=" + ((Fragment)object).getView());
        while (mSavedState.size() <= position) {
    
    
            mSavedState.add(null);
        }
        mSavedState.set(position, fragment.isAdded()
                ? mFragmentManager.saveFragmentInstanceState(fragment) : null);
        mFragments.set(position, null);

		// 缓存中移除fragment,下次使用得重新创建
        mCurTransaction.remove(fragment);
        if (fragment.equals(mCurrentPrimaryItem)) {
    
    
            mCurrentPrimaryItem = null;
        }
    }

FragmentStatePagerAdapterEle foi removido quando destroyItemo método foi chamado Fragment, portanto precisa ser recriado na próxima vez.Fragment

3 Fragmento + ViewPager2

ViewPager2É Android Jetpackum componente da biblioteca e é um contêiner usado para implementar a troca de páginas e efeitos deslizantes no aplicativo. Na verdade, ViewPager2ele próprio é herdado deRecyclerView . Você pode revisar nossos [Android Framework Series], Capítulo 12, princípios relacionados ao RecycleView e análise de estratégia de cache de quatro níveis

3.1 A função e o propósito do ViewPager2

3.1.1 Cenários de uso do ViewPager2

É um contêiner deslizante poderoso que pode ser usado em uma variedade de cenários. Ele fornece troca de página flexível e funções de personalização de layout, tornando a interface do aplicativo mais rica e interativa. Ele pode ser usado nos seguintes cenários:

  1. A implementação de páginas de integração ou de boas-vindas
    ViewPager2pode ser usada para criar páginas de integração ou de boas-vindas que permitem aos usuários deslizar para apresentar recursos do aplicativo ou exibir conteúdo de boas-vindas.

  2. Criar um navegador de imagens
    ViewPager2pode ser usado para criar um navegador de imagens, permitindo aos usuários alternar entre diferentes imagens deslizando e suporta zoom e interação por gestos.

  3. A construção de carrosséis
    ViewPager2é muito adequada para a construção de funções de carrossel, que podem carregar dinamicamente diferentes itens do carrossel por meio de adaptadores e fornecer rolagem automática do ciclo.

  4. Implementando layout com guias
    A combinação pode ser usada para criar um layout com guias, permitindo que os usuários alternem entre diferentes páginas de conteúdo deslizando as guias TabLayout.ViewPager2

  5. Crie uma página deslizante vertical
    Ao contrário de ViewPager, ViewPager2ela suporta deslizamento vertical, portanto pode ser usada para criar layouts de página deslizante vertical, como menus de navegação deslizante vertical ou listas de notícias verticais.

  6. A implementação da exibição de dados de paginação
    ViewPager2pode ser usada para exibir dados de paginação, como carregar uma grande quantidade de dados por página e exibir uma parte do conteúdo em cada página.

  7. O layout deslizante aninhado
    ViewPager2pode ser usado aninhado com outros componentes deslizantes (como RecyclerView) para implementar estruturas complexas de layout deslizante.

  8. Obtenha efeitos deslizantes personalizados
    Ao usar um conversor personalizado ( Transformer), você pode obter vários efeitos interessantes de troca de página, como gradiente, zoom, rotação, etc.

3.1.2 Melhorias e vantagens do ViewPager2 em comparação ao ViewPager

ViewPager2é ViewPageruma versão melhorada do original, oferecendo melhor desempenho, adaptadores mais flexíveis e funcionalidades mais ricas. É o componente preferido para a construção de layouts de páginas deslizantes. Ele pode implementar vários requisitos de páginas deslizantes em aplicativos e fornecer uma melhor experiência ao usuário. Possui as seguintes melhorias e vantagens:

  1. O suporte para deslizamento vertical
    ViewPager2foi ViewPageraprimorado e uma das melhorias mais significativas é 支持垂直滑动. No ViewPagerAndroid, apenas o deslizamento horizontal é compatível. Isto torna ViewPager2a função de deslizamento vertical mais conveniente e flexível ao criar layouts verticais ou cenários específicos.

  2. Melhor desempenho e estabilidade
    ViewPager2A implementação interna é usada RecyclerViewcomo contêiner e não depende mais ViewPagerda implementação. Isto a vantagem ViewPager2de , e. Ao mesmo tempo, alguns problemas e instabilidades conhecidos também foram resolvidos , como posições de entrada incorretas e atualização intempestiva de dados.RecyclerView更好的性能和内存管理更流畅的滑动体验及更好的布局回收和复用机制ViewPager2ViewPager

  3. Suporta o uso de Fragment como uma página.
    Ao contrário de Fragment ViewPager, ViewPager2ele suporta diretamente o uso Fragmentde Fragment como uma página sem passar FragmentPagerAdapterou FragmentStatePagerAdapteradaptar. Isso simplifica o gerenciamento de páginas e o manuseio do ciclo de vida e fornece uma experiência de uso mais intuitiva e consistente.

  4. Adaptadores mais flexíveis
    ViewPager2introduzem novas interfaces de adaptador, RecyclerView.Adaptersubclasses de RecyclerView.Adapter. Isso torna a criação e o gerenciamento de adaptadores mais flexíveis, ao mesmo tempo que fornece mais funcionalidade e extensibilidade.

  5. Funções e interfaces mais ricas
    ViewPager2fornecem muitas funções e interfaces novas, como suporte para pré-carregamento de página, suporte mais poderoso para animação de troca de página, interfaces de retorno de chamada mais ricas, etc. Esses recursos e interfaces oferecem aos desenvolvedores maior controle e personalização ViewPager2de comportamento e aparência.

3.2 FragmentStateAdapter

Acima aprendemos sobre algumas diferenças e pontos de ViewPager2comparação de otimização ViewPager, vamos continuar a examinar ViewPager2os correspondentes Adapter.
Sabemos que ViewPager2ele herda de RecyclerView, então seu correspondente FragmentStateAdapterdeve herdar de RecyclerView.Adapter, o que os amigos devem ser capazes de entender. Então, RecyclerViewcada Itemjunção que adicionamos Fragmenté apresentada como um contêiner.
RecyclerView.AdapterO foco está ViewHolderno reaproveitamento, mas os que estão dentro não FragmentStateAdaptersão reaproveitados, ou seja, devem ser criados Framgenttantos quantos houver , então como convertê-los?itemFragment

Vamos dar uma olhada no código-fonte primeiro FragmentStateAdapter:

public abstract class FragmentStateAdapter extends
        RecyclerView.Adapter<FragmentViewHolder> implements StatefulAdapter {
    
    
        
    // 通过FragmentStateAdapter声明中的泛型可以知道,
    // ViewPager2之所以能够在RecyclerView的基础上能对外屏蔽对ViewHolder的使用,
    // 其内部是借助FragmentViewHolder实现的,其内部就new了一个FrameLayout。
    @Override
    public final FragmentViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    
    
        return FragmentViewHolder.create(parent);
    }

    @Override
    public final void onBindViewHolder(final @NonNull FragmentViewHolder holder, int position) {
    
    
        final long itemId = holder.getItemId();
        final int viewHolderId = holder.getContainer().getId();
        final Long boundItemId = itemForViewHolder(viewHolderId); // item currently bound to the VH
        if (boundItemId != null && boundItemId != itemId) {
    
    
            removeFragment(boundItemId);
            mItemIdToViewHolder.remove(boundItemId);
        }

        mItemIdToViewHolder.put(itemId, viewHolderId); // this might overwrite an existing entry
        // 内部会最终回调到createFragment用来创建当前Fragment
        ensureFragment(position);

        /** Special case when {@link RecyclerView} decides to keep the {@link container}
         * attached to the window, but not to the view hierarchy (i.e. parent is null) */
        final FrameLayout container = holder.getContainer();
        if (ViewCompat.isAttachedToWindow(container)) {
    
    
            if (container.getParent() != null) {
    
    
                throw new IllegalStateException("Design assumption violated.");
            }
            container.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
    
    
                @Override
                public void onLayoutChange(View v, int left, int top, int right, int bottom,
                        int oldLeft, int oldTop, int oldRight, int oldBottom) {
    
    
                    if (container.getParent() != null) {
    
    
                        container.removeOnLayoutChangeListener(this);
                        placeFragmentInViewHolder(holder);
                    }
                }
            });
        }
		
		// 回收已经不在item集合中的Fragment,节省内存开销
        gcFragments();
    }

   	private void ensureFragment(int position) {
    
    
        long itemId = getItemId(position);
        if (!mFragments.containsKey(itemId)) {
    
    
            // TODO(133419201): check if a Fragment provided here is a new Fragment
            Fragment newFragment = createFragment(position);
            newFragment.setInitialSavedState(mSavedStates.get(itemId));
            mFragments.put(itemId, newFragment);
        }
    }
    
    void gcFragments() {
    
    
        if (!mHasStaleFragments || shouldDelayFragmentTransactions()) {
    
    
            return;
        }

        // Remove Fragments for items that are no longer part of the data-set
        Set<Long> toRemove = new ArraySet<>();
        for (int ix = 0; ix < mFragments.size(); ix++) {
    
    
            long itemId = mFragments.keyAt(ix);
            if (!containsItem(itemId)) {
    
    
                toRemove.add(itemId);
                mItemIdToViewHolder.remove(itemId); // in case they're still bound
            }
        }

        // Remove Fragments that are not bound anywhere -- pending a grace period
        if (!mIsInGracePeriod) {
    
    
            mHasStaleFragments = false; // we've executed all GC checks

            for (int ix = 0; ix < mFragments.size(); ix++) {
    
    
                long itemId = mFragments.keyAt(ix);
                if (!isFragmentViewBound(itemId)) {
    
    
                    toRemove.add(itemId);
                }
            }
        }

        for (Long itemId : toRemove) {
    
    
            removeFragment(itemId);
        }
    }


	// onViewAttachToWindow的时候调用placeFragmentInViewHolder,
	// 将FragmentViewHolder的container与当前Fragment绑定
    @Override
    public final void onViewAttachedToWindow(@NonNull final FragmentViewHolder holder) {
    
    
        placeFragmentInViewHolder(holder);
        gcFragments();
    }

 	void placeFragmentInViewHolder(@NonNull final FragmentViewHolder holder) {
    
    
        Fragment fragment = mFragments.get(holder.getItemId());
        if (fragment == null) {
    
    
            throw new IllegalStateException("Design assumption violated.");
        }
        FrameLayout container = holder.getContainer();
        View view = fragment.getView();

        if (!fragment.isAdded() && view != null) {
    
    
            throw new IllegalStateException("Design assumption violated.");
        }

        // { f:added, v:notCreated, v:notAttached} -> schedule callback for when created
        if (fragment.isAdded() && view == null) {
    
    
            scheduleViewAttach(fragment, container);
            return;
        }

        // { f:added, v:created, v:attached } -> check if attached to the right container
        if (fragment.isAdded() && view.getParent() != null) {
    
    
            if (view.getParent() != container) {
    
    
                addViewToContainer(view, container);
            }
            return;
        }

        // { f:added, v:created, v:notAttached} -> attach view to container
        if (fragment.isAdded()) {
    
    
            addViewToContainer(view, container);
            return;
        }

        // { f:notAdded, v:notCreated, v:notAttached } -> add, create, attach
        if (!shouldDelayFragmentTransactions()) {
    
    
            scheduleViewAttach(fragment, container);
            mFragmentManager.beginTransaction()
                    .add(fragment, "f" + holder.getItemId())
                    .setMaxLifecycle(fragment, STARTED)
                    .commitNow();
            mFragmentMaxLifecycleEnforcer.updateFragmentMaxLifecycle(false);
        } else {
    
    
            if (mFragmentManager.isDestroyed()) {
    
    
                return; // nothing we can do
            }
            mLifecycle.addObserver(new LifecycleEventObserver() {
    
    
                @Override
                public void onStateChanged(@NonNull LifecycleOwner source,
                        @NonNull Lifecycle.Event event) {
    
    
                    if (shouldDelayFragmentTransactions()) {
    
    
                        return;
                    }
                    source.getLifecycle().removeObserver(this);
                    if (ViewCompat.isAttachedToWindow(holder.getContainer())) {
    
    
                        placeFragmentInViewHolder(holder);
                    }
                }
            });
        }
    }
    
}
public final class FragmentViewHolder extends ViewHolder {
    
    
    private FragmentViewHolder(@NonNull FrameLayout container) {
    
    
        super(container);
    }

	// FragmentViewHolder实际上就创建了一个FrameLayout
    @NonNull static FragmentViewHolder create(@NonNull ViewGroup parent) {
    
    
        FrameLayout container = new FrameLayout(parent.getContext());
        container.setLayoutParams(
                new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                        ViewGroup.LayoutParams.MATCH_PARENT));
        container.setId(ViewCompat.generateViewId());
        container.setSaveEnabled(false);
        return new FragmentViewHolder(container);
    }

    @NonNull FrameLayout getContainer() {
    
    
        return (FrameLayout) itemView;
    }
}

A partir da análise do código-fonte acima, podemos saber que embora Fragmentnão seja reutilizado, a exibição alternativa é obtida ViewHolderpor meio da reutilização.containerFramgent

4 Resumo

Para resumir o conteúdo deste capítulo:

  1. Fragment+ViewPagerEle só pode rolar horizontalmente e o desempenho é relativamente ruim. Correspondentes a diferentes Adapterefeitos são diferentes.
  2. Fragment+ViewPagerQuando usado FragmentPagerAdapter, o exterior do escopo fragmentsserá salvo na memória ( detach), mas fragmentos correspondentes Viewserão destruídos e fragmentsos correspondentes SavedStateserão salvos.A FragmentPagerAdaptermemória é maior, mas a troca de página é mais amigável, adequada para Fragmentpequenas quantidades.
  3. Fragment+ViewPagerQuando usado FragmentStatePagerAdapter, fragmentsele não será salvo na memória( remove) fora do escopo e Viewtambém será destruído. fragmentsApenas os correspondentes dentro do intervalo são salvos SavedState. Isso é usado para parâmetros externos a serem passados ​​no retorno de chamada do ciclo de vida e SavedStateé semelhante. O uso de memória é pequeno e a troca de páginas é um pouco ruim. Adequado para grandes quantidades.FragmentActivityFragment
  4. Fragment+ViewPager2Ele pode rolar horizontalmente ou verticalmente e herda RecyclerViewbasicamente todas as suas vantagens, incluindo uso de memória, gerenciamento de cache, etc. FragmentStateAdapterHerdado de RecyclerView.Adapter, embora Fragmentnão seja reutilizado, a exibição alternativa é implementada ViewHolderpor meio da reutilização.containerFramgent

Acho que você gosta

Origin blog.csdn.net/u010687761/article/details/132674389
Recomendado
Clasificación