我能否使用视图翻页器与视图(而不是片段)一起使用?

150

我正在使用 ViewPagerFragments 之间进行滑动,但是我能否使用 ViewPager 在简单的 XML 布局中滑动 Views

这是我的页面适配器 Adapter,用于在 ViewPager 中滑动 Fragments:

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);
    }

}

这是我的提示片段:

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;
    }
}

我该如何修改我的代码,使其适用于Views而不是Fragment?


请看这个例子https://dev59.com/P14c5IYBdhLWcg3wEGt_#37916222 - Zar E Ahmer
为什么不使用片段?如果我们使用或不使用片段,会得到或失去什么? - Eftekhari
3
@Eftekhari 碎片 => 复杂的生命周期 => 更多的错误 => 混乱 - Harshil Pansare
2
@HarshilPansare 是的,我在二月份提出这个问题后经历了所有这些灾难,我将不再在我的项目中使用片段。我别无选择,只能在 onDestroy 中清除所有片段,因此在 onResume 活动中将不需要检索可能不再可用的所有 3 个片段。只想提及其中一个问题。 - Eftekhari
1
干杯,迎接无碎片的生活! - Harshil Pansare
9个回答

101

你需要重写这两个方法,而不是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);
}

6
太好了,你帮了我很大的忙。我使用了PageAdapter而不是FragmentPageAdapter......现在它运行得很好。 - ranjith
3
请参考这个问题中所提供的代码示例,以获取完整的可运行示例:https://dev59.com/QGw05IYBdhLWcg3wZBAC - Bitcoin Cash - ADA enthusiast
7
为了好奇,为什么需要使用addView? Fragment适配器只是要求返回视图。 - Amit Gupta
2
你根本不需要将其转换为 ViewPager,因为你正在处理 ViewGroup 接口。 - Dori
2
@AmitGupta,你需要将视图添加到容器中,因为你可能没有在这里返回视图。instantiateItem必须返回与该视图相关联的对象,如果你愿意,可以是不同的对象;它是一个关键点。无论你返回什么,你只需确保你的isViewFromObject实现可以将两者匹配,即关键点和视图。然而,最常见的实现方法是将视图作为关键点同时返回。FragmentPageAdapter会为你处理所有的关键点到视图的事情,因此只要求你创建一个片段即可。 - themightyjon
显示剩余6条评论

72

使用这个示例。

您可以使用一个XML布局嵌套子视图。

 <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>

但是...你还需要使用一个适配器来处理。在这里,我们返回找到的视图ID,而不会膨胀任何其他布局。

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
    }
}

// 设置ViewPager的适配器

WizardPagerAdapter adapter = new WizardPagerAdapter();
ViewPager pager = (ViewPager) findViewById(R.id.pager);
pager.setAdapter(adapter);

2
@user1672337 findViewById方法可以从Activity类中访问。因此,在您的Activity中创建一个内部(非静态)类。或者您必须将Activity实例传递给适配器。 - ruX
8
如果ViewPager有超过两个子视图,似乎这样做不起作用。例如,如果我的ViewPager中有三个RelativeLayout,并尝试划到第三页,第三页显示为空白。任何想法是怎么回事? - Nathan Walters
17
@NathanWalters,今天我也遇到了同样的问题,解决方法是增加ViewPager的OffscreenPageLimit属性。可能值得更新一下答案,加入这个信息。 - Mikhail
2
如果您不想传递活动实例,可以使用return collection.findViewById(resId); - Aksiom
3
请添加以下代码:@Override public void destroyItem(ViewGroup container, int position, Object object) {},此代码用于销毁ViewPager中指定位置的页面。 - Volodymyr Kulyk
显示剩余6条评论

13

我们已经构建了一个非常简单的ViewPager子类,有时我们会使用它。

/**
 * 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) {}
        });
    }

}

这个类不需要适配器,因为它会从布局中加载视图。为了在您的项目中使用它,只需将其替换 android.support.v4.view.ViewPager 即可。

所有花哨的东西仍然会起作用,但您不需要为适配器烦恼。


2
这个对我来说几乎有效,但我不得不将 onAttachedToWindow() 更改为 onFinishInflate() - ehehhh
@Eftekhari,不使用片段更容易编写,可以减少编写的代码量,这意味着后期阅读和理解更轻松,并且更少出现错误。 - Sabo

12

根据之前的答案,我制作了以下类以最恰当和清晰的方式实现这一点(希望如此):

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);
    }
}

你需要在你的活动中设置它:

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();
    }

XML文件所在的位置很明显,就是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>

而ActivityMain具有以下布局:

<?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>

非常感谢Brian和Nicholas的回答,我希望我能补充一些更清晰的信息,并强调一些好的实践方法来支持这个功能。


1
不是很好,它正在保存所有已创建的视图。如果只保存可见的视图,会更好(类似于ListView中的convertview)。 - htafoya

6
我想在这里提供我的解决方案。假设您不需要使用片段,仍然可以创建一个PagerAdapter,将views而不是fragments附加到ViewPager上。
扩展PagerAdapter而不是FragmentPagerAdapter
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);
  }
}

现在,您需要定义三个类,它们将返回要在viewpager中填充的views。 类似于CpuView,您将拥有MemoryViewNetworkView类。 它们每个都将填充其各自的布局。

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);
  }
}

最后是一个布局,将在每个视图中膨胀

    <?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>
P.S.:我写这篇答案的原因是因为所有提供的解决方案似乎都能正常工作,但它们会在PagerAdapter类本身中膨胀布局。对于大型项目,如果与充气布局相关的代码很多,就会变得难以维护。现在,在这个示例中,所有视图都有单独的类和单独的布局。因此,该项目可以轻松维护。

在我看来,活动更易于使用。 - Denny

4

我想详细说明@Nicholas的答案,您可以通过id获取视图,或者如果它们是动态添加的,只需根据其位置直接获取视图。

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);
    }
}

4

0

0
在ViewPager2中,您根本不需要使用片段。只需像创建RecyclerView适配器一样创建一个适配器即可,并将其设置为ViewPager2。
但是要记住的一件事是,您想要填充的视图的布局高度应该是match_parent而不是像对待RecyclerView一样的wrap_content。
Snap行为也是相同的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接