Posso usar o paginador de visualizações com visualizações (não com fragmentos)

131

Estou usando ViewPagerpara passar entre Fragments, mas posso usar ViewPagerpara passar entre Viewslayout XML simples?

Esta é a minha página Adapterdo ViewPager, usada para deslizar entre os fragmentos:

import java.util.List;

import com.app.name.fragments.TipsFragment;

import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.app.FragmentTransaction;
import android.view.ViewGroup;

public class PageAdapter extends FragmentPagerAdapter {

    /**
     *
     */
    List<Fragment> fragments;
    public PageAdapter(FragmentManager fm,List<Fragment> frags) {
        super(fm);
        fragments = frags;

    }

    @Override
    public Fragment getItem(int arg0) {
        // TODO Auto-generated method stub
        return TipsFragment.newInstance(0, 0);
    }

    @Override
    public int getCount() {
        // TODO Auto-generated method stub
        return 4;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        FragmentManager manager = ((Fragment) object).getFragmentManager();
        FragmentTransaction trans = manager.beginTransaction();
        trans.remove((Fragment) object);
        trans.commit();

        super.destroyItem(container, position, object);
    }

}

E este é o meu fragmento de dica:

public class TipsFragment extends Fragment
{
    public static TipsFragment newInstance(int image,int content)
    {
        TipsFragment fragment = new TipsFragment();
        return fragment;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.tip_layout, null);
        return view;
    }
}

Como posso modificar meu código para trabalhar com Views em vez de Fragment?

ranjith
fonte
também dê uma olhada neste exemplo stackoverflow.com/a/37916222/3496570
Zar E Ahmer 20/16
Por que não usar fragmentos? O que alcançaremos ou perderemos se usarmos ou não fragmentos?
Eftekhari
@Eftekhari Fragments => Ciclo de vida complexo => Mais bugs => Caos
Harshil Pansare
1
@HarshilPansare Sim, passei por todos esses desastres depois de fazer essas perguntas em fevereiro e não vou mais usar fragmentos em meus projetos. Eu não tinha escolha, mas para limpar todos os fragmentos do ViewPageron onDestroy, portanto, sobre onResumea atividade não haverá necessidade de recuperar todos os 3 fragmentos que podem já não está disponível. Só queria mencionar um dos problemas.
Eftekhari
Saúde à vida sem fragmentos!
Harshil Pansare

Respostas:

95

Você precisa substituir esses dois métodos em vez de getItem():

@Override
public Object instantiateItem(ViewGroup collection, int position) {
    View v = layoutInflater.inflate(...);
    ...
    collection.addView(v,0);
    return v;
}

@Override
public void destroyItem(ViewGroup collection, int position, Object view) {
    collection.removeView((View) view);
}
Biraj Zalavadia
fonte
5
nice one .. ajuda-me muito ... i estendida PageAdapter vez de FragmentPageAdapter ........ agora o seu belo trabalho .....
ranjith
3
Para um exemplo de trabalho completo, confira o código encontrado nesta pergunta: stackoverflow.com/q/7263291
Tiago
7
Por curiosidade, por que o addView é necessário. O adaptador de fragmento pediu para retornar a exibição.
Amit Gupta
2
Você não precisa elenco para ViewPagera todos como você está lidando com a ViewGroupinterface de
Dori
2
@AmitGupta, é necessário adicionar a visualização ao contêiner, pois talvez você não esteja retornando a visualização aqui. instantiateItem deve retornar um objeto associado a essa exibição, que pode ser um objeto diferente, se você preferir; é uma chave. Seja o que for que você devolver, certifique-se de que sua implementação de isViewFromObject possa corresponder às duas chaves acima para exibição. No entanto, a implementação mais comum é apenas retornar a exibição como a chave. O FragmentPageAdapter lida com todo o material chave para exibir para você e, portanto, apenas pede que você crie um fragmento.
themightyjon
66

Use este exemplo

Você pode usar um único layout XML aninhando as visualizações filhas.

 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <android.support.v4.view.ViewPager
            android:id="@+id/pager"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <LinearLayout
                android:id="@+id/page_one"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="vertical" >
                        <TextView
                        android:text="PAGE ONE IN"
                        android:layout_width="match_parent"
                        android:layout_height="match_parent"
                        android:textColor="#fff"
                        android:textSize="24dp"/>
            </LinearLayout>

            <LinearLayout
                android:id="@+id/page_two"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="vertical" >
                        <TextView
                        android:text="PAGE TWO IN"
                        android:layout_width="match_parent"
                        android:layout_height="match_parent"
                        android:textColor="#fff"
                        android:textSize="24dp"/>
            </LinearLayout>

    </android.support.v4.view.ViewPager>
</LinearLayout>

MAS ... você precisa lidar com isso com um adaptador também. Aqui retornamos o ID da visualização encontrada sem inflar nenhum outro layout.

class WizardPagerAdapter extends PagerAdapter {

    public Object instantiateItem(ViewGroup collection, int position) {

        int resId = 0;
        switch (position) {
        case 0:
            resId = R.id.page_one;
            break;
        case 1:
            resId = R.id.page_two;
            break;
        }
        return findViewById(resId);
    }

    @Override
    public int getCount() {
        return 2;
    }

    @Override
    public boolean isViewFromObject(View arg0, Object arg1) {
        return arg0 == arg1;
    }

    @Override public void destroyItem(ViewGroup container, int position, Object object) {
        // No super
    }
}

// Defina o adaptador do ViewPager

WizardPagerAdapter adapter = new WizardPagerAdapter();
ViewPager pager = (ViewPager) findViewById(R.id.pager);
pager.setAdapter(adapter);
Nicholas Betts
fonte
2
@ user1672337 findViewById pode ser acessado na classe Activity. Portanto, faça uma classe interna (não estática) em sua atividade. Ou você tem que passar instância de atividade ao adaptador
Rux
7
Isso não parece funcionar se o ViewPager tiver mais de 2 visualizações filho. Por exemplo, se eu tiver três RelativeLayouts no meu ViewPager e tentar deslizar para a terceira página, a terceira página será exibida em branco. Alguma idéia do que dá?
Nathan Walters
15
@NathanWalters, tive o mesmo problema hoje, resolvi-o aumentando a propriedade OffscreenPageLimit do ViewPager. Provavelmente vale a pena atualizar uma resposta com essas informações.
21416 Mikhail
2
você pode usar return collection.findViewById(resId);se você não quer passar uma instância de atividade
Aksiom
3
Por favor, adicione este código@Override public void destroyItem(ViewGroup container, int position, Object object) {}
Volodymyr Kulyk 18/11
11

Criamos uma subclasse muito simples do ViewPagerque usamos algumas vezes.

/**
 * View pager used for a finite, low number of pages, where there is no need for
 * optimization.
 */
public class StaticViewPager extends ViewPager {

    /**
     * Initialize the view.
     *
     * @param context
     *            The application context.
     */
    public StaticViewPager(final Context context) {
        super(context);
    }

    /**
     * Initialize the view.
     *
     * @param context
     *            The application context.
     * @param attrs
     *            The requested attributes.
     */
    public StaticViewPager(final Context context, final AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();

        // Make sure all are loaded at once
        final int childrenCount = getChildCount();
        setOffscreenPageLimit(childrenCount - 1);

        // Attach the adapter
        setAdapter(new PagerAdapter() {

            @Override
            public Object instantiateItem(final ViewGroup container, final int position) {
                return container.getChildAt(position);
            }

            @Override
            public boolean isViewFromObject(final View arg0, final Object arg1) {
                return arg0 == arg1;

            }

            @Override
            public int getCount() {
                return childrenCount;
            }

            @Override
            public void destroyItem(final View container, final int position, final Object object) {}
        });
    }

}

Esta classe não precisa de um adaptador, pois carregará as visualizações do layout. Para usá-lo em seus projetos, basta usá-lo em vez de android.support.v4.view.ViewPager.

Todo o material sofisticado ainda funcionará, mas você não precisa se preocupar com os adaptadores.

Sabo
fonte
1
Isso quase funcionou para mim, mas eu tive que mudar onAttachedToWindow()para onFinishInflate().
ehehhh
@Eftekhari É mais fácil sem fragmentos e você escrever menos código, o que significa que é mais fácil de ler e entender mais tarde e menos propenso a erros
Sabo
11

Com base nas respostas anteriores, fiz a seguinte aula para conseguir isso de maneira adequada e clara (espero):

public class MyViewPagerAdapter extends PagerAdapter {

    ArrayList<ViewGroup> views;
    LayoutInflater inflater;

    public MyViewPagerAdapter(ActionBarActivity ctx){
        inflater = LayoutInflater.from(ctx);
        //instantiate your views list
        views = new ArrayList<ViewGroup>(5);
    }

    /**
     * To be called by onStop
     * Clean the memory
     */
    public void release(){
     views.clear();
        views = null;
    }

    /**
     * Return the number of views available.
     */
    @Override
    public int getCount() {
        return 5;
    }

    /**
     * Create the page for the given position. The adapter is responsible
     * for adding the view to the container given here, although it only
     * must ensure this is done by the time it returns from
     * {@link #finishUpdate(ViewGroup)}.
     *
     * @param container The containing View in which the page will be shown.
     * @param position The page position to be instantiated.
     * @return Returns an Object representing the new page. This does not
     *         need to be a View, but can be some other container of
     *         the page.  ,container
     */
    public Object instantiateItem(ViewGroup container, int position) {
        ViewGroup currentView;
        Log.e("MyViewPagerAdapter", "instantiateItem for " + position);
        if(views.size()>position&&views.get(position) != null){
            Log.e("MyViewPagerAdapter",
                  "instantiateItem views.get(position) " +
                  views.get(position));
            currentView = views.get(position);
        }
        else{
            Log.e("MyViewPagerAdapter", "instantiateItem need to create the View");
            int rootLayout = R.layout.view_screen;
            currentView = (ViewGroup) inflater.inflate(rootLayout, container, false);

            ((TextView)currentView.findViewById(R.id.txvTitle)).setText("My Views " + position);
            ((TextView)currentView.findViewById(R.id.btnButton)).setText("Button");
            ((ImageView)currentView.findViewById(R.id.imvPicture)).setBackgroundColor(0xFF00FF00);
        }
        container.addView(currentView);
        return currentView;
    }

    /**
     * Remove a page for the given position. The adapter is responsible
     * for removing the view from its container, although it only must ensure
     * this is done by the time it returns from {@link #finishUpdate(ViewGroup)}.
     *
     * @param container The containing View from which the page will be removed.
     * @param position The page position to be removed.
     * @param object The same object that was returned by
     * {@link #instantiateItem(View, int)}.
     */
    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        container.removeView((View)object);

    }

    /**
     * Determines whether a page View is associated with a specific key object
     * as returned by {@link #instantiateItem(ViewGroup, int)}. This method is
     * required for a PagerAdapter to function properly.
     *
     * @param view   Page View to check for association with <code>object</code>
     * @param object Object to check for association with <code>view</code>
     * @return true if <code>view</code> is associated with the key object <code>object</code>
     */
    @Override
    public boolean isViewFromObject(View view, Object object) {
        return view==((View)object);
    }
}

E você deve configurá-lo em sua atividade:

public class ActivityWithViewsPaged extends ActionBarActivity {

    /**
     * The page Adapter: Manage the list of views (in fact here, its fragments)
     * And send them to the ViewPager
     */
    private MyViewPagerAdapter pagerAdapter;

    /**
     * The ViewPager is a ViewGroup that manage the swipe from left
     * to right to left.
     * Like a listView with a gesture listener...
     */
    private ViewPager viewPager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_with_views);

        // Find the viewPager
        viewPager = (ViewPager) super.findViewById(R.id.viewpager);

        // Instantiate the PageAdapter
        pagerAdapter = new MyViewPagerAdapter(this);

        // Affectation de l'adapter au ViewPager
        viewPager.setAdapter(pagerAdapter);
        viewPager.setClipToPadding(false);
        viewPager.setPageMargin(12);

        // Add animation when the page are swiped
        // this instanciation only works with honeyComb and more
        // if you want it all version use AnimatorProxy of the nineoldAndroid lib
        //@see:http://stackoverflow.com/questions/15767729/backwards-compatible-pagetransformer
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB){
            viewPager.setPageTransformer(true, new PageTransformer());
        }
    }

    @Override
    protected void onStop() {
        super.onStop();
        pagerAdapter.release();
    }

Onde os arquivos XML são óbvios view_screen.xml:

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

 <TextView
        android:id="@+id/txvTitle"
        android:layout_width="wrap_content"
        android:layout_gravity="center"
        android:layout_height="wrap_content"
        android:layout_marginBottom="5dp"
        android:layout_marginTop="5dp"
        android:shadowColor="#FF00FF"
        android:shadowDx="10"
        android:shadowDy="10"
        android:shadowRadius="5"
        android:textSize="32dp"
        android:textStyle="italic"
        android:background="#FFFFF000"/>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#FFFF00F0">
        <TextView
            android:id="@+id/txvLeft"
            android:layout_width="wrap_content"
            android:layout_gravity="left"
            android:layout_height="wrap_content"
            android:layout_marginBottom="5dp"
            android:layout_marginTop="5dp"/>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"/>
        <TextView
            android:id="@+id/txvRight"
            android:layout_width="wrap_content"
            android:layout_gravity="right"
            android:layout_height="wrap_content"
            android:layout_marginBottom="5dp"
            android:layout_marginTop="5dp"/>
    </LinearLayout>
    <Button
        android:id="@+id/btnButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"/>
    <ImageView
        android:id="@+id/imvPicture"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="center"/>
</LinearLayout>

E o ActivtyMain possui o seguinte layout:

<?xml version="1.0" encoding="utf-8"?>

<android.support.v4.view.ViewPager
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:paddingLeft="24dp"
    android:paddingRight="24dp"
    android:id="@+id/viewpager"
    android:background="#FF00F0F0">
</android.support.v4.view.ViewPager>

Muito obrigado a Brian e Nicholas por sua resposta, espero adicionar algumas informações mais claras e destacar algumas boas práticas para esse recurso.

Mathias Seguy Android2ee
fonte
1
Não é muito bom, você está salvando todas as visualizações criadas. Seria melhor se ele salvasse apenas as visualizações visíveis. (semelhante ao convertview em listview)
htafoya
4

Gostaria de elaborar a resposta do @Nicholas, você pode obter as visualizações por ID ou, se forem adicionadas dinamicamente, basta obtê-las diretamente, dada sua posição

class WizardPagerAdapter extends PagerAdapter {

    public Object instantiateItem(View collection, int position) {

        View v = pager.getChildAt(position);

        return v;
    }

    @Override
    public int getCount() {
        return 3;
    }

    @Override
    public boolean isViewFromObject(View arg0, Object arg1) {
        return arg0 == ((View) arg1);
    }
}
Brian
fonte
4

Eu gostaria de adicionar minha solução aqui. Como você não precisa usar fragmentos, ainda é possível criar um PagerAdapterque anexa em viewsvez de fragmentsao ViewPager.

Estender em PagerAdaptervez deFragmentPagerAdapter

public class CustomPagerAdapter extends PagerAdapter {

  private Context context;

  public CustomPagerAdapter(Context context) {
    super();
    this.context = context;
  }


  @Override
  public Object instantiateItem(ViewGroup collection, int position) {
    LayoutInflater inflater = LayoutInflater.from(context);
    View view = null;
    switch (position){
      case 0:
        view = MemoryView.getView(context, collection);
        break;
      case 1:
        view = NetworkView.getView(context, collection);
        break;
      case 2:
        view = CpuView.getView(context, collection);
        break;
    }

    collection.addView(view);
    return view;
  }

  @Override
  public int getCount() {
    return 3;
  }

  @Override
  public boolean isViewFromObject(View view, Object object) {
    return view==object;
  }

  @Override
  public void destroyItem(ViewGroup collection, int position, Object view) {
    collection.removeView((View) view);
  }
}

Agora você precisa definir três classes que retornarão o viewsa ser inflado no viewpager. Semelhante a CpuViewvocê terá MemoryViewe NetworkViewaulas. Cada um deles inflará seus respectivos layouts.

public class CpuView {

public static View getView(Context context, ViewGroup collection) {

    LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context
        .LAYOUT_INFLATER_SERVICE);
    return inflater.inflate(R.layout.debugger_cpu_layout, collection, false);
  }
}

E, finalmente, um layout que será inflado em cada uma das vistas

    <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="#000000"
        android:text="CPU"/>
</LinearLayout>

PS: A razão pela qual escrevi esta resposta é porque todas as soluções fornecidas aqui parecem estar funcionando bem, mas elas estão inflando os layouts na própria classe PagerAdapter. Para projetos grandes, fica difícil manter se há muito código relacionado aos layouts inflados. Agora, neste exemplo, todas as visualizações têm classes e layouts separados. Portanto, o projeto pode ser facilmente mantido.

thedarkpassenger
fonte
IMO, as atividades são mais fáceis de usar
Denny