在项目的进展中使用了Gallery控件展示相册,由于Gallery控件已经被谷歌弃用,所以建议大家考虑使用新的控件来代替,及时跟上潮流。不过本篇的重点不是讨论被弃用的Gallery控件,而是来讨论怎么使用Gallery控件实现类似ListView控件的刷新和加载功能,因为项目里面使用的是PullToRefresh的库,但是该库好像并不支持Gallery的刷新和加载功能,所以只得自己参考别人的写法来实现该功能了。本文提供一种实现思路,希望可以帮到大家。(源码附在文后,欢迎大家下载,不要分!!)
先看下效果图(用的是在线GIF生成工具):
下面开始正文。首先讲一下实现的思路:Gallery是横向滑动的控件,我们类似ListView实现刷新一样,当滑动到第0项时候,开始刷新操作,滑动到最后一项的时候进行分页加载的操作。
顺着这个思路,我们开始编码了:首先是一头一尾两个界面,刷新界面和加载更多的界面,两个界面布局类似,就只贴一个布局文件了。
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <PRogressBar style="?android:attr/progressBarStyleLarge" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_centerHorizontal="true" android:layout_marginTop="124dp" android:id="@+id/progressBarLoadMore" /> <TextView android:text="@string/loadMore" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_below="@+id/progressBarLoadMore" android:layout_marginTop="30dp" android:textSize="24sp" android:layout_centerHorizontal="true" android:id="@+id/textView2" /></RelativeLayout>写完之后大概长这个样子,当然你可以根据自己的喜好去定制
原来的Gallery控件肯定不能实现我们想要的效果,所以下面我们来定制自己的Gallery,对原来的Galley稍稍动一些手脚改造一下。接着上代码:
public class MyGallery extends Gallery implements android.widget.AdapterView.OnItemSelectedListener{ private IGalleryEventListener mIGalleryEventListener; // 监听Gallery滑动 private View refreshView ; // 刷新的缓冲界面 private View loadMoreView ; // 加载更多缓冲的界面 private int startIndex = 0 ; // 记录首项的索引 private int endIndex = 0 ; // 记录最后一项的索引 public MyGallery(Context context) { super(context); this.setOnItemSelectedListener(this); // 设置监听器 } public MyGallery(Context context, AttributeSet attrs) { super(context, attrs); this.setOnItemSelectedListener(this); // 设置监听器 } public MyGallery(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.setOnItemSelectedListener(this); // 设置监听器 } /** * 对外提供设置监听器的方法 * @param iGalleryEventListener Gallery的滑动监听器 */ public void setGalleryEventListerner(IGalleryEventListener iGalleryEventListener){ this.mIGalleryEventListener = iGalleryEventListener; } /** * 刷新或加载结束的回调方法 */ public void onCompleted(){ if(refreshView != null && refreshView.isShown()){ refreshView.setVisibility(View.GONE); // 刷新页面不可见 this.setSelection(startIndex+1); // 选中刷新页面的后一项 } if(loadMoreView != null && loadMoreView.isShown()){ loadMoreView.setVisibility(View.GONE); // 加载更多页面不可见 if(this.getChildAt(endIndex) != null ) this.setSelection(endIndex) ; // 有数据就选中刚刚加载的一项 else this.setSelection(endIndex-1); // 否则就选中加载更多页面的前面一项 } } @Override public void onItemSelected(AdapterView<?> adapterView, View view, int position, long id) { startIndex = 0 ; // 首项 endIndex = adapterView.getCount() - 1 ; // 最后一项 if(position == startIndex ){ // 表示滑动到了最前面一页,刷新操作 refreshView = view ; // 刷新页面保存 if(!refreshView.isShown()) refreshView.setVisibility(View.VISIBLE); mIGalleryEventListener.onRefresh(); // 回调刷新的方法 } if(position == endIndex){ // 表示滑动到了最后一页,加载操作 loadMoreView = view ; if(!loadMoreView.isShown()) loadMoreView.setVisibility(View.VISIBLE); mIGalleryEventListener.onLoadMore(); // 回调加载的方法 } } @Override public void onNothingSelected(AdapterView<?> adapterView) { }}注释已经写得很清楚了,红色部分是我们自己定义的一个接口,有两个待实现的方法(加载更多和刷新),红色部分的方法给当前的Gallery提供设置这个接口的方法,后面要用到。蓝色部分是结束时候的回调方法,紫色部分是当前的Gallery实现了OnItemSelectedListener接口的一些处理,当滑动到最前面一页的时候让刷新界面出现,当滑动到最后一页的时候让加载更多的界面出现。public interface IGalleryEventListener { public void onLoadMore(); // 加载更多的回调函数 public void onRefresh(); // 刷新的回调函数}上面是监听的接口,包含两个待实现的方法,后面再要使用的地方实现该接口即可。下面写一下Gallery的适配器public class MyGalleryAdapter extends BaseAdapter { private Context mContext ; // 运行的上下文 private List<PictureBean> mPictureList ; // 实体类数据集合 private LayoutInflater mInfalter ; // 界面渲染器 private View refreshView, loadMoreView; // 一头一尾 public MyGalleryAdapter(){ } /** * 构造方法 * @param context 上下文 * @param pictureList 实体类数据集合 */ public MyGalleryAdapter(Context context , List<PictureBean> pictureList){ this.mContext = context ; this.mPictureList = pictureList ; } @Override public int getCount() { return mPictureList.size() + 2; // 这里多加了两个是因为我们多加了两个界面,分别是加载和刷新的界面,在数据集合中一头一尾 } @Override public PictureBean getItem(int position) { if(position==0 || position== getCount()-1) return null; return mPictureList.get(position-1) ; } @Override public long getItemId(int i) { return i; } @Override public View getView(int position, View view, ViewGroup viewGroup) { mInfalter = LayoutInflater.from(mContext); if(position == 0 ){ // 第一页 refreshView = mInfalter.inflate(R.layout.refresh_layout ,null); refreshView.setVisibility(View.GONE); // 默认设置刷新页面不可见 return refreshView; // 返回刷新页面 }else if(position == this.getCount() - 1){ // 最后一页 loadMoreView = mInfalter.inflate(R.layout.loadmore_layout ,null); loadMoreView.setVisibility(View.GONE); // 默认设置加载更多不可见 return loadMoreView; // 返回加载更多页面 }else{ if(view == null) view = mInfalter.inflate(R.layout.content_layout, null); ImageView imageView = ViewHolder.get(view,R.id.image); TextView describeTv = ViewHolder.get(view,R.id.describeTv); imageView.setImageResource(getItem(position).getPictureResId()); // 设置相片资源ID describeTv.setText(getItem(position).getPictureName()); // 设置相片名称 return view ; // 返回内容页面 } }}上面代码注意红色加粗的部分,在原来的数据集合长度上在加上2,表示一头一尾各加了一个布局占位。蓝色部分也很好理解,初始的时候让一头一尾的两个界面都隐藏起来,该显示的时候才让其显示。
这里有一个R.layout.content_layout的布局,如下,包含一个ImageView和一个TextView
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="10dp" android:gravity="center"> <TextView android:id="@+id/describeTv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/image" android:layout_centerHorizontal="true" android:layout_marginTop="20dp" android:text="TextView" android:textSize="24sp" /> <ImageView android:id="@+id/image" android:layout_width="300dp" android:layout_height="360dp" android:src="@mipmap/ic_launcher" android:layout_centerInParent="true" /></RelativeLayout>下面开始看MainActivity的布局界面
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="com.cjt.galleryrefreshdemo.MainActivity"> <com.cjt.galleryrefreshdemo.view.MyGallery android:id="@+id/myGallery" android:layout_width="match_parent" android:layout_height="match_parent" android:spacing="15dp" android:layout_alignParentTop="true" android:layout_alignParentRight="true" android:layout_alignParentEnd="true" android:layout_marginRight="17dp" android:layout_marginEnd="17dp"> </com.cjt.galleryrefreshdemo.view.MyGallery></RelativeLayout>这里使用的是我们刚刚自定义的Gallery布局,那么MainActivity要使用刷新和加载的功能,就不得不实现我们刚刚定义的接口了,代码如下public class MainActivity extends AppCompatActivity implements IGalleryEventListener{ private MyGallery myGallery ; // 自定义Gallery控件 private MyGalleryAdapter myGalleryAdapter ; // 适配器 private List<PictureBean> pictureList = new ArrayList<>(); // 数据集合,使用前要先转换为List的子类 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); myGallery = (MyGallery) findViewById(R.id.myGallery); /// 获取界面自定义的Gallery控件 myGallery.setGalleryEventListerner(this); // 为Gallery设置滑动监听器 // 准备数据 PictureBean bean ; for (int i = 1; i <= 7 ; i++) { bean = new PictureBean(); // 下面一句是获取mipmap中对应的图片名称的资源ID int picResId = this.getResources().getIdentifier("pic"+i,"mipmap",getPackageName()); bean.setPictureResId(picResId); bean.setPictureName("照片--"+i); pictureList.add(bean); } // 设置适配器 myGalleryAdapter = new MyGalleryAdapter(this,pictureList) ; myGallery.setAdapter(myGalleryAdapter); myGallery.setSelection(1); // 默认选中第一项,跳过第0项 } @Override public void onLoadMore() { // 在这里处理加载更多的事件 taskThread(0); } @Override public void onRefresh() { // 在这里处理刷新的事件 taskThread(1); } /** * 一般新开一个线程用于加载或刷新操作 * @param type 这里的type用于区分是刷新还是加载操作 */ private void taskThread(final int type){ new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } Log.e("CJT","这里可以新建加载或刷新的任务"); getLoad(type); // 这里是模拟加载更多的任务 } }).start(); } // 模拟获取数据的方法,这里一般是网络请求加载数据 private void getLoad(int type){ if(type == 0){ // 加载操作 mHandler.sendEmptyMessage(0x01); // 加载完毕之后,发送消息,更新界面 // 这里模拟加载更多数据 // 准备数据 PictureBean bean ; for (int i = 1; i <= 4 ; i++) { bean = new PictureBean(); int picResId = this.getResources().getIdentifier("m"+i,"mipmap",getPackageName()); bean.setPictureResId(picResId); bean.setPictureName("美女--"+i); pictureList.add(bean); } }else{ mHandler.sendEmptyMessage(0x02); // 刷新完毕之后,发送消息,更新界面 } } // 一般使用Handler来更新界面 private Handler mHandler = new Handler(){ @Override public void handleMessage(Message msg) { if(msg.what == 0x01){ // 更新界面 if(myGalleryAdapter != null ) myGalleryAdapter.notifyDataSetChanged(); }else if(msg.what == 0x02){ // 更新界面 } myGallery.onCompleted(); // 同时调用加载结束的回调函数,停止转圈圈 } };}首先实现了我们自定义的接口,并且重写了两个方法(刷新和加载),在刷新和加载中我们开启了一个新的线程,模拟在后台实现的网络请求过程,加入线程休眠Thread.sleep(2000)主要是让刷新界面或者加载界面都能出来露个脸,就是让圈圈出来转一下。getLoad()方法就是模拟我们的后台请求数据,我这里使用一个int类型用来区分是刷新请求还是加载更多的请求,当然你们可以根据自己的需要去增加相应的请求的参数,比如分页加载需要页码(pageNum)等。推荐使用Handler来更新界面,如果是加载更多的话,只需要调用Adapter的notifyDataSetChanged()方法,如果是刷新的话根据业务需求来更新,比如我是将数据集合清空,然后在填充一次,这样避免了数据重复。最后别忘了回调加载结束的方法,不然那个刷新或加载更多的圈圈会一直不停的转、不停的转、不停的转……。这里有一个测试用的实体类,也一并贴上来,显得文章长一些…………
public class PictureBean { private int pictureResId ; // 图片资源的ID private String pictureName ; // 图片的名称描述 public int getPictureResId() { return pictureResId; } public void setPictureResId(int pictureResId) { this.pictureResId = pictureResId; } public String getPictureName() { return pictureName; } public void setPictureName(String pictureName) { this.pictureName = pictureName; } @Override public String toString() { return "PictureBean{" + "pictureResId=" + pictureResId + ", pictureName='" + pictureName + '/'' + '}'; }}好像也没见得有多长。总结一下:其实实现可滑动控件的刷新或加载也不难,定义好接口以及相应的待实现的方法,在要使用的地方实现该接口,并填充方法,在方法里面做刷新或加载的操作。另外刷新或加载完了之后,推荐使用Handler来更新界面,至于转圈圈的界面,控制好时机,该出现的时候让他出现,不该出现的时候坚决将他干掉就行了。
参考: 感谢网上的资料,下次记得把链接和名字一并奉上,这次就低调些。
想要源码的可以去这两个地方,不要分,拿走不谢。我也是个菜鸟,希望跟大家共同探讨人生,哈哈哈。
Git地址:https://github.com/1989Jiangtao/GalleryRefresh
代码下载:http://download.csdn.net/detail/u010898329/9750269
新闻热点
疑难解答