在项目的进展中使用了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 ; // 返回内容页面
}
}
}
蓝色部分也很好理解,初始的时候让一头一尾的两个界面都隐藏起来,该显示的时候才让其显示。
这里有一个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.youkuaiyun.com/detail/u010898329/9750269