GridView+Fragment+ViewPager最佳实践

本文详细介绍GridView与ViewPager在Android开发中的应用。GridView适用于多行多列的网状布局,展示大量图片;ViewPager则常用于实现图片滑动效果。文章通过具体步骤演示如何实现这两种组件,并介绍如何在点击GridView中的图片时,跳转到ViewPager展示选中的图片。

概述

GridView是一种有效显示大量图片的列表容器,用法也跟ListView类似,它的布局是一个网格,一行可以有多个项,并且整个视图可以滚动,我们常见的应用有手机中的图库、launcher里面的应用列表、类似微信多张图片等,总的来说,ListView主要应用于单列多行的列表,然而GridView主要应用于多行多列的网状布局,它能够一次显示许多图片,同时即将被显示的图片会处于准备显示的状态。

GridView显示图片

下面是一个典型的使用场景,在Fragment里面内置GridView,其中GridView的子视图是ImageView。

实现效果:我们让图片以网格的样式显示出来,点击图片,则显示这张图片。

应用明细部分的实现

我们首先完成应用的明细部分,即显示某张选中的图片。

设计一个名为ImageDetailFragment的Fragment来管理下图的用户界面,再设计一个名为ImageDetailActivity的activity来托管ImageDetailFragment实例。这里我们使用动态加载的方式。

这里写图片描述

Step1 :定义定义容器视图

修改ImageDetailActivity的布局文件如下,fragment_activity.xml

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

FrameLayout是服务于ImageDetailFragment的容器视图。注意容器视图是通用性视图,不局限于ImageDetailFragment类,我们可以并且也将使用同一个布局来托管其他的fragment。

step 2:定义 ImageDetailFragment 的布局

布局文件 fragment_image_detail.xml,用于显示某张具体的图片:

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

    <ImageView
        android:id="@+id/mImageView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:src="@mipmap/ic_launcher"/>

</LinearLayout>

step 3:创建 ImageDetailFragment类

public class ImageDetailFragment extends Fragment {
    private ImageView mImageView;
    ...
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.fragment_deatil_image,container,false);
        mImageView = (ImageView)v.findViewById(R.id.mImageView);
        return v;
    }
     ...
}

step 4:添加UI Fragment到FragmentManager

public class ImageDetailActivity extends SingleFragmentActivity {
    public static final String EXTRA_IMAGE = "extra_image";
    ...
    @Override
    protected Fragment createFragment() {
        return new ImageDetailFragment();
    }

}

这里我们把托管UI Fragment的代码抽取出来,封装成一个通用的抽象类SingleFragmentActivity类,这样子我们下一次托管另一个Fragment的时候就方便多了。

SingleFragmentActivity.java:

public abstract class SingleFragmentActivity extends FragmentActivity {
    protected abstract Fragment createFragment();

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.fragment_activity);
        FragmentManager fm = getFragmentManager();
        Fragment fragment = fm.findFragmentById(R.id.fragment_container);
        if (fragment == null) {
            fragment = createFragment();
            fm.beginTransaction()
                    .add(R.id.fragment_container, fragment)
                    .commit();
        }
    }
}

GridView显示图片部分的实现

step 1:实现ImageGridActivity类

public class ImageGridActivity extends SingleFragmentActivity {
    @Override
    protected Fragment createFragment() {
        return new ImageGridFragment();
    }
}

step 2:创建ImageGridFragment类

public class ImageGridFragment extends Fragment {
// Nothing yet
}

step 3:创建GridView布局

fragment_image_grid.xml :

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

    <GridView
        android:id="@+id/photo_wall"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:columnWidth="90dp"
        android:stretchMode="columnWidth"
        android:numColumns="auto_fit"
        android:verticalSpacing="10dip"
        android:gravity="center"></GridView>

</LinearLayout>

android:numColumns=”auto_fit” //GridView的列数设置为自动
android:columnWidth=”90dp ” //每列的宽度,也就是Item的宽度
android:stretchMode=”columnWidth”//缩放与列宽大小同步
android:verticalSpacing=”10dp” //两行之间的边距

step 3:创建GridView子项布局

layout_grid_item.xml :

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

    <ImageView
        android:id="@+id/iv_head"
        android:layout_width="90dp"
        android:layout_height="90dp"
        android:layout_centerInParent="true"
        android:scaleType="centerCrop"/>

</LinearLayout>

step 4:为ImageGridFragment配置视图(ImageGridFrament.java):

public class ImageGridFragment extends Fragment {

    public final static Integer[] imageResIds = new Integer[] {
            R.mipmap.simple_image_1, R.mipmap.simple_image_2, R.mipmap.simple_image_3,
            R.mipmap.simple_image_4, R.mipmap.simple_image_5, R.mipmap.simple_image_6,
            R.mipmap.simple_image_7, R.mipmap.simple_image_8, R.mipmap.simple_image_9};

   public  ImageGridFragment(){

   };

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        final View v = inflater.inflate(R.layout.fragment_image_grid,container,false);
        final GridView mGridView = (GridView) v.findViewById(R.id.photo_wall);
        return v;
    }
...
}

step 4:为ImageGridFragment实现Adapter和点击事件(ImageGridFrament.java):

public class ImageGridFragment extends Fragment implements AdapterView.OnItemClickListener {
    private ImageAdapter mAdapter;

    public final static Integer[] imageResIds = new Integer[] {
            R.mipmap.simple_image_1, R.mipmap.simple_image_2, R.mipmap.simple_image_3,
            R.mipmap.simple_image_4, R.mipmap.simple_image_5, R.mipmap.simple_image_6,
            R.mipmap.simple_image_7, R.mipmap.simple_image_8, R.mipmap.simple_image_9};

   public  ImageGridFragment(){

   };

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mAdapter = new ImageAdapter(getActivity());
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        final View v = inflater.inflate(R.layout.fragment_image_grid,container,false);
        final GridView mGridView = (GridView) v.findViewById(R.id.photo_wall);
        mGridView.setAdapter(mAdapter);
        //设置监听器
        mGridView.setOnItemClickListener(this);
        return v;
    }

    @Override
    //点击事件
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
       Toast.makeText(getActivity(),"you clicked!",Toast.LENGTH_SHORT).show();
}
    }

    private class ImageAdapter extends BaseAdapter{
        private  final Context mContext;
        public ImageAdapter(Context context) {
            super();
            this.mContext = context;
        }

        @Override
        public int getCount() {
            return imageResIds.length;
        }

        @Override
        public Object getItem(int position) {
            return imageResIds[position];
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder viewHolder = null;
            ImageView imageView;
            if(convertView==null) {
                LayoutInflater inflater = getActivity().getLayoutInflater();
                convertView = inflater.inflate(R.layout.layout_grid_item,parent,false);
                viewHolder = new ViewHolder();
                viewHolder.imageView = (ImageView)convertView.findViewById(R.id.iv_head);

                convertView.setTag(viewHolder);
            }
            else{
               viewHolder = (ViewHolder)convertView.getTag();

            }
            viewHolder.imageView.setImageResource(imageResIds[position]);
            return convertView;
        }

        private class ViewHolder {
            ImageView imageView;
        }
    }
}

适配器的用法和ListView几乎一模一样。

关联列表和明细

应用的列表和明细我们都已经完成了,接下来要做的就是如何关联两个部分。

step 1:从Fragment中启动Activity( ImageGridFragment ):

fragment 中 启 动 activity 类 似 于 从 activity 中 启 动 activity 。 我 们 调 用 Fragment.startActivity(Intent)方法,然后在后台触发调用对应的Activity方法。

在ImageGridFragment的Adapter类里, 用启动ImageDetailActivity实例的代码,替换Toast消息处理代码,

public class ImageGridFragment extends Fragment implements AdapterView.OnItemClickListener {
    ...

 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            Intent i = new Intent(getActivity(), ImageDetailActivity.class);
            startActivity(i);
    }

private class ImageAdapter extends BaseAdapter{
    ...
    }
}

指定要启动的activity为ImageDetailActivityImageGridFragment创建了一个显式intent。至于Intent构造方法需要的Context对象, ImageGridFragment是使用getActivity()方法传入它
的托管activity来满足的。

step 2:附加 extra 信息( ImageDetailActivity ):

现在我们点击任意一张图片都能进入到ImageDetailActivity类,但是因为不知道用户点击的是哪张图片,因此ImageDetailActivity总是会显示一张默认的ic_luncher图片。

启动ImageDetailActivity时,传递附加到Intent extra上的int positionImageDetailFragment就能知道该显示哪张图片,这需要在ImageDetailActivity中新增newIntent方法:

public class ImageDetailActivity extends SingleFragmentActivity {
    public static final String EXTRA_IMAGE = "extra_image";

    public static Intent newIntent(Context packageContext, int imagePosition){
        Intent intent = new Intent(packageContext,ImageDetailActivity.class);
        intent.putExtra(EXTRA_IMAGE,imagePosition);
        return intent;
    }
    ...
}

创建了显式intent后,调用putExtra(…)方法,传入匹配position的字符串键与键值。 这里,由于position是Integer对象,我们需要调用putExtra(String,Int)方法。

step 3:更新OnItenonClick()方法,使用newIntent新方法( ImageGrideFragment):

public class ImageGridFragment extends Fragment implements AdapterView.OnItemClickListener {
    ...

 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
           Intent i = ImageDetailActivity.newIntent(getActivity(), position);
            startActivity(i);
    }
 ...
 }   

step 3:获取 extra 信息( ImageDetailFragment):

我们在onItemClick()方法中调用newIntent()方法时,传入用户点击的图片的位置position,现在position已经安全地存储到ImageDetailActivityIntent中,然而,要获取和使用extra信息的是ImageDetailFragment类。

我们使用fragment argument来获取extra数据,看这里

编写newInstance(int)方法(ImageDetailFragment.java)

public class ImageDetailFragment extends Fragment {
    private static final String IMAGE_DATA_EXTRA = "resId";
    private int mImageNum;
    private ImageView mImageView;

    public static ImageDetailFragment newInstance(int imageNum){
        Bundle args = new Bundle();
        args.putInt(IMAGE_DATA_EXTRA,imageNum);

        ImageDetailFragment fragment = new ImageDetailFragment();
        fragment.setArguments(args);
        return fragment;
    }
    ...
  }

使用newInstance(Int)方法( ImageDetailActvity ):

public class ImageDetailActivity extends SingleFragmentActivity {
    public static final String EXTRA_IMAGE = "extra_image";
    ...

    @Override
    protected Fragment createFragment() {
        int position = getIntent().getIntExtra(EXTRA_IMAGE,0);
        return ImageDetailFragment.newInstance(position);
    }

}

现在我们就将int position传入了argument之中。

step 4:获取 argument 中的数据( ImageDetailFragment):

public class ImageDetailFragment extends Fragment {
    private static final String IMAGE_DATA_EXTRA = "resId";
    private int mImageNum;
    private ImageView mImageView;
    ...

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mImageNum = getArguments() != null ? getArguments().getInt(IMAGE_DATA_EXTRA) : -1;
    }
    ...
 }

至此我们就完成了我们的效果:

这里写图片描述

这里写图片描述

使用ViewPager

在应用中我们有时也会使用滑动来切换显示不同图片,我们可以通过PagerAdapterViewPager控件来实现这个效果。 不过,一个更加合适的Adapter是PagerAdapter的一个子类,叫FragmentStatePagerAdapter:它可以在某个ViewPager中的子视图切换出屏幕时自动销毁与保存Fragments的状态。这样能够保持更少的内存消耗。

如果只有为数不多的图片并且确保不会超出程序内存限制,那么使用PagerAdapter或 FragmentPagerAdapter会更加合适。

step 1:创建ImagePagerActivity:

我们新建一个名为ImagePagerActivity的新建activity取代ImageDetailActivity。其布局由一个ViewPager组成。

image_detail_pager.xml:

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

    <android.support.v4.view.ViewPager
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </android.support.v4.view.ViewPager>

</LinearLayout>

step 2:创建ViewPager(ImagePagerActivity.java):

public class ImagePagerActivity extends FragmentActivity {
    public static final String EXTRA_IMAGE = "extra_image";

    private ImagePagerAdapter mAdapter;
    private ViewPager mPager;
    private int imagePosition;

    // A static dataset to back the ViewPager adapter
    public final static Integer[] imageResIds = new Integer[] {
            R.mipmap.simple_image_1, R.mipmap.simple_image_2, R.mipmap.simple_image_3,
            R.mipmap.simple_image_4, R.mipmap.simple_image_5, R.mipmap.simple_image_6,
            R.mipmap.simple_image_7, R.mipmap.simple_image_8, R.mipmap.simple_image_9};


 public static Intent newIntent(Context packageContext, int imagePosition){
        Intent intent = new Intent(packageContext,ImagePagerActivity.class);
        intent.putExtra(EXTRA_IMAGE,imagePosition);
        return intent;
    }


     @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.image_detail_pager); // Contains just a ViewPager
        mPager = (ViewPager) findViewById(R.id.pager);

    }
    ...
}

step 3:设置pager adapter(ImagePagerActivity.java):

public class ImagePagerActivity extends FragmentActivity {
    public static final String EXTRA_IMAGE = "extra_image";

    private ImagePagerAdapter mAdapter;
    private ViewPager mPager;
    private int imagePosition;

    // A static dataset to back the ViewPager adapter
    public final static Integer[] imageResIds = new Integer[] {
            R.mipmap.simple_image_1, R.mipmap.simple_image_2, R.mipmap.simple_image_3,
            R.mipmap.simple_image_4, R.mipmap.simple_image_5, R.mipmap.simple_image_6,
            R.mipmap.simple_image_7, R.mipmap.simple_image_8, R.mipmap.simple_image_9};

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.image_detail_pager); // Contains just a ViewPager

        mAdapter = new ImagePagerAdapter(getFragmentManager(), imageResIds.length);
        mPager = (ViewPager) findViewById(R.id.pager);
        mPager.setAdapter(mAdapter);
    }

    public static class ImagePagerAdapter extends FragmentStatePagerAdapter {
        private final int mSize;

        public ImagePagerAdapter(FragmentManager fm, int size) {
            super(fm);
            mSize = size;
        }

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

        @Override
        public Fragment getItem(int position) {
            return ImageDetailFragment.newInstance(position);
        }

    }
}

FragmentStatePagerAdapter化繁为简,提供了两个有用的方法: getCount()getItem(int)。调用getItem(int)方法,获取并显示imageResIds数组中指定位置的图片时,它会返回配置过的ImageDetailFragment来完成显示任务。

FragmentStatePagerAdapter是我们的代理,负责管理与ViewPager的对话并协同工作。代理需首先将getItem(int)方法返回的fragment添加给activity,然后才能使用fragment完成自己的工作。这也就是创建代理实例时,需要FragmentManager的原因。

(代理究竟做了哪些工作呢?简单来说,就是将返回的fragment添加给托管activity,并帮助viewpager找到fragment的视图并一一对应。)

pager adapter的两个方法简单直接。 getCount()方法返回数组列表中包含的列表项数目。
getItem(int)方法有着神奇的魔法。它首先获取数据集中指定位置的图片实例,然后利用该图片的位置创建并返回一个有效配置的ImageDetailFragment。

step4:配置启动ImagePagerActivity(ImageGrideFragment.java):

 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            //Intent i = GridDetailActivity.newIntent(getActivity(), position);
            Intent i = ImagePagerActivity.newIntent(getActivity(),position);
            startActivity(i);
    }

至此我们就完成了图片的滑动,但是有一个小问题,每次点击图片都会显示第一张图片,那是因为ViewPager默认只显示PageAdapter中的第一个列表项。要显示选中的列表项,可设置ViewPager当前要显示的列表项为涂片数组中指定位置的列表项。

step5:设置初始分页显示项(ImagePagerActivity.java):

public class ImageDetailActivity extends FragmentActivity {
    public static final String EXTRA_IMAGE = "extra_image";

    private ImagePagerAdapter mAdapter;
    private ViewPager mPager;
    private int imagePosition;

    ...

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.image_detail_pager); // Contains just a ViewPager
        //获得存储在Intent中的点击位置
        imagePosition = getIntent().getIntExtra(EXTRA_IMAGE,0);

          ...
        //设置显示指定位置的列表项即图片
        mPager.setCurrentItem(imagePosition);
    }
    ...
}

有了这两行代码,选择任意图片,其对应的图片应该能够显示了,并且左右滑动可以查看其他图片。

源码在这里。有点大,主要是图片的锅~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值