求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
  
 
 
     
   
分享到
Android照片墙加强版,使用ViewPager实现画廊效果
 
作者 guolin的博客,火龙果软件    发布于 2014-05-12
 

记得关于照片墙的文章我已经写过好几篇了,有最基本的照片墙,有瀑布流模式的照片墙,后来又在瀑布流的基础之上加入了查看大图和多点触控缩放的功能。总体来说,照片墙这个Demo在这几篇文章的改进中已经变得较为完善了,本想关于这个功能的系列到此为止,但有朋友跟我反应,觉得在查看大图的时候最好能通过左右滑动来浏览前后的图片。恩,确实,好像比较高端的一些应用都有这样的效果,那么本篇文章中我们来继续对照片墙这个Demo进行改进,让它变得更加高端大气上档次!

整理了一下思路,感觉自己去实现一套通过左右滑动来切换图片的功能非常不划算,需要编写不少的代码。这里为了要让实现简单化,我们准备使用Android提供的ViewPager来完成这个功能。

ViewPager的基本用法我就不在本文中介绍了,如果还不了解的朋友可以到王鹏兄那里先学习一下 http://blog.csdn.net/wangjinyu501/article/details/8169924 。

另外,本篇文章的代码是完全在之前文章的基础上进行开发的,所以如果你还没有看过我前面所写的关于照片墙的文章,建议先去阅读一下 Android瀑布流照片墙实现,体验不规则排列的美感 和 Android多点触控技术实战,自由地对图片进行缩放和移动 这两篇文章。

下面就让我们开始动手吧,打开PhotoWallFallsDemo这个项目,首先修改image_details.xml这个布局文件中的代码,如下所示:

<?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.support.v4.view.ViewPager
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</android.support.v4.view.ViewPager>
<TextView
android:id="@+id/page_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="10dp"
android:textColor="#fff"
android:textSize="18sp" />
</RelativeLayout>

这里我们在布局文件中放置了两个控件,ViewPager和TextView,其中ViewPager自然是用来管理所有的图片的了,而TextView则是用于显示当前图片的页数以及总页数。

然后新建一个zoom_image_layout.xml,在这里放入ZoomImageView控件,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<com.example.photowallfallsdemo.ZoomImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/zoom_image_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000000" >
</com.example.photowallfallsdemo.ZoomImageView>

接下来修改ImageDetailsActivity中的代码,在这里去实现ViewPager的具体功能,代码如下所示:

public class ImageDetailsActivity extends Activity implements OnPageChangeListener {
/**
* 用于管理图片的滑动
*/
private ViewPager viewPager;
/**
* 显示当前图片的页数
*/
private TextView pageText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.image_details);
int imagePosition = getIntent().getIntExtra("image_position", 0);
pageText = (TextView) findViewById(R.id.page_text);
viewPager = (ViewPager) findViewById(R.id.view_pager);
ViewPagerAdapter adapter = new ViewPagerAdapter();
viewPager.setAdapter(adapter);
viewPager.setCurrentItem(imagePosition);
viewPager.setOnPageChangeListener(this);
// 设定当前的页数和总页数
pageText.setText((imagePosition + 1) + "/" + Images.imageUrls.length);
}
/**
* ViewPager的适配器
*
* @author guolin
*/
class ViewPagerAdapter extends PagerAdapter {
@Override
public Object instantiateItem(ViewGroup container, int position) {
String imagePath = getImagePath(Images.imageUrls[position]);
Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
if (bitmap == null) {
bitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.empty_photo);
}
View view = LayoutInflater.from(ImageDetailsActivity.this).inflate(
R.layout.zoom_image_layout, null);
ZoomImageView zoomImageView = (ZoomImageView) view
.findViewById(R.id.zoom_image_view);
zoomImageView.setImageBitmap(bitmap);
container.addView(view);
return view;
}
@Override
public int getCount() {
return Images.imageUrls.length;
}
@Override
public boolean isViewFromObject(View arg0, Object arg1) {
return arg0 == arg1;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
View view = (View) object;
container.removeView(view);
}
}
/**
* 获取图片的本地存储路径。
*
* @param imageUrl
* 图片的URL地址。
* @return 图片的本地存储路径。
*/
private String getImagePath(String imageUrl) {
int lastSlashIndex = imageUrl.lastIndexOf("/");
String imageName = imageUrl.substring(lastSlashIndex + 1);
String imageDir = Environment.getExternalStorageDirectory().getPath()
+ "/PhotoWallFalls/";
File file = new File(imageDir);
if (!file.exists()) {
file.mkdirs();
}
String imagePath = imageDir + imageName;
return imagePath;
}
@Override
public void onPageScrollStateChanged(int arg0) {
}
@Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
}
@Override
public void onPageSelected(int currentPage) {
// 每当页数发生改变时重新设定一遍当前的页数和总页数
pageText.setText((currentPage + 1) + "/" + Images.imageUrls.length);
}
}

这个类也是实现滑动切换图片功能最主要的一个类了,由于ViewPager的用法并不复杂,所以这个类的代码也不多,下面我们来仔细地分析一下。

首先在onCreate()方法中要去加载我们刚刚修改的image_details.xml布局,然后要从Intent中取出当前要展示的那张图片的位置。接下来通过findViewById()方法获取到ViewPager和TextView控件的实例,并创建了一个ViewPagerAdapter对象作为ViewPager的适配器,之后去调用setCurrentItem()方法来设置当前显示的是哪一张图片。

那么这个ViewPagerAdapter又是什么呢?可以看到,它是一个继承了PagerAdapter的适配器,是专门用于在ViewPager中使用的。一般情况下我们都需要至少去重写PagerAdapter中的instantiateItem()、getCount()、isViewFromObject()和destroyItem()这四个方法。在instantiateItem()方法中,我们根据图片的位置获取到了图片对应的存储路径,然后调用BitmapFactory的解析方法将这张图片解析成一个Bitmap对象,接着实例化zoom_image_layout.xml这个布局,并获取其中的ZoomImageView控件,然后把Bitmap对象设定进去。在getCount()方法,只是简单地返回了一共有多少张图片。isViewFromObject()方法比较简单,就是判断两个参数是否相等就好。而destroyItem()方法中,则是要把应该销毁的View对象回收掉,以防止图片过多导致OOM出现。

另外,这里的ViewPager还注册了OnPageChangeListener接口,每当ViewPager的页数发现改变时,onPageSelected()方法就会调用。我们在这里让TextView显示当前图片的页数以及总页数即可。

目前的ImageDetailsActivity已经具备了翻页浏览图片的功能了,如果你心急的话,可以现在就运行试一试。不过一但你运行之后,就会发现,我们还有一些细节工作还没完成。比如说在onCreate()方法中会从Intent中取出要显示的那张图片的位置,而很明显目前是取不到了。于是,我们还需要修改MyScrollView中的代码,在这里将点击的那张图片的位置传递过来。由于这个类中的代码非常多,我只列出需要修改的那些部分,如下所示:

public class MyScrollView extends ScrollView implements OnTouchListener {
......
/**
* 开始加载下一页的图片,每张图片都会开启一个异步线程去下载。
*/
public void loadMoreImages() {
if (hasSDCard()) {
int startIndex = page * PAGE_SIZE;
int endIndex = page * PAGE_SIZE + PAGE_SIZE;
if (startIndex < Images.imageUrls.length) {
Toast.makeText(getContext(), "正在加载...", Toast.LENGTH_SHORT).show();
if (endIndex > Images.imageUrls.length) {
endIndex = Images.imageUrls.length;
}
for (int i = startIndex; i < endIndex; i++) {
LoadImageTask task = new LoadImageTask();
taskCollection.add(task);
task.execute(i);
}
page++;
} else {
Toast.makeText(getContext(), "已没有更多图片", Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(getContext(), "未发现SD卡", Toast.LENGTH_SHORT).show();
}
}
/**
* 遍历imageViewList中的每张图片,对图片的可见性进行检查, 如果图片已经离开屏幕可见范围,则将图片替换成一张空图。
*/
public void checkVisibility() {
for (int i = 0; i < imageViewList.size(); i++) {
ImageView imageView = imageViewList.get(i);
int borderTop = (Integer) imageView.getTag(R.string.border_top);
int borderBottom = (Integer) imageView.getTag(R.string.border_bottom);
if (borderBottom > getScrollY() && borderTop < getScrollY() + scrollViewHeight) {
String imageUrl = (String) imageView.getTag(R.string.image_url);
Bitmap bitmap = imageLoader.getBitmapFromMemoryCache(imageUrl);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
} else {
LoadImageTask task = new LoadImageTask(imageView);
task.execute(i);
}
} else {
imageView.setImageResource(R.drawable.empty_photo);
}
}
}
......
/**
* 异步下载图片的任务。
*
* @author guolin
*/
class LoadImageTask extends AsyncTask<Integer, Void, Bitmap> {
/**
* 记录每个图片对应的位置
*/
private int mItemPosition;
......
@Override
protected Bitmap doInBackground(Integer... params) {
mItemPosition = params[0];
mImageUrl = Images.imageUrls[mItemPosition];
Bitmap imageBitmap = imageLoader.getBitmapFromMemoryCache(mImageUrl);
if (imageBitmap == null) {
imageBitmap = loadImage(mImageUrl);
}
return imageBitmap;
}
......
/**
* 向ImageView中添加一张图片
*
* @param bitmap
* 待添加的图片
* @param imageWidth
* 图片的宽度
* @param imageHeight
* 图片的高度
*/
private void addImage(Bitmap bitmap, int imageWidth, int imageHeight) {
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(imageWidth,
imageHeight);
if (mImageView != null) {
mImageView.setImageBitmap(bitmap);
} else {
ImageView imageView = new ImageView(getContext());
imageView.setLayoutParams(params);
imageView.setImageBitmap(bitmap);
imageView.setScaleType(ScaleType.FIT_XY);
imageView.setPadding(5, 5, 5, 5);
imageView.setTag(R.string.image_url, mImageUrl);
imageView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(getContext(), ImageDetailsActivity.class);
intent.putExtra("image_position", mItemPosition);
getContext().startActivity(intent);
}
});
findColumnToAdd(imageView, imageHeight).addView(imageView);
imageViewList.add(imageView);
}
}

......

}
}

可以看到,这里我们将LoadImageTask 的泛型进行了修改,doInBackground不再接收一个字符串数组,而是接收一个整型数组,这里传入的参数也就代表着每张图片的位置。这样的话,每个调用LoadImageTask 的地方也都需要进行相应的修改,在loadMoreImages()和checkVisibility()方法中,都将传入的参数改成了图片的位置。最后在addImage()方法中,使用Intent将点击的那张图片对应的位置传递给了ImageDetailsActivity。

目前看上去一切都完美了吧!但其实还有一点工作我们还没完成。由于ViewPager的事件和ZoomImageView的事件是存在冲突的,所以加入了ViewPager后,ZoomImageView本身的单个手指拖动图片的功能会受很大的影响。所以我们还需要在ZoomImageView的onTouchEvent()中进行判断,如果当前的图片是没有缩放的,则允许通过滑动来切换图片,如果当前的图片已经放大了,则要屏蔽掉ViewPager的事件,这样ZoomImageView本身的事件就不会受影响。代码如下所示:

public class ZoomImageView extends View {
......
@Override
public boolean onTouchEvent(MotionEvent event) {
if (initRatio == totalRatio) {
getParent().requestDisallowInterceptTouchEvent(false);
} else {
getParent().requestDisallowInterceptTouchEvent(true);
}
......
return true;
}
......
}

这里使用getParent()获取到的就是ViewPager对象,然后调用requestDisallowInterceptTouchEvent()方法来启动和禁用ViewPager的功能。

好了,这样的话,所有的代码就已经完成了,可以运行一下看看完整的效果了。点击任意一张图片可以查看大图,然后通过左右滑动可以浏览前后的图片,并且仍然能够通过多点触控对图片进行缩放,效果如下图所示:

除了滑动切换图片之外,在屏幕的底部还能显示当前图片的页数以及总页数,功能已经是相当完善了。目前这个照片墙Demo的效果已经不亚于市场上一些常见的图片浏览程序了吧。

 
相关文章

手机软件测试用例设计实践
手机客户端UI测试分析
iPhone消息推送机制实现与探讨
Android手机开发(一)
 
相关文档

Android_UI官方设计教程
手机开发平台介绍
android拍照及上传功能
Android讲义智能手机开发
相关课程

Android高级移动应用程序
Android系统开发
Android应用开发
手机软件测试
 
分享到
 
 


android人机界面指南
Android手机开发(一)
Android手机开发(二)
Android手机开发(三)
Android手机开发(四)
iPhone消息推送机制实现探讨
手机软件测试用例设计实践
手机客户端UI测试分析
手机软件自动化测试研究报告
更多...   


Android高级移动应用程序
Android应用开发
Android系统开发
手机软件测试
嵌入式软件测试
Android软、硬、云整合


领先IT公司 android开发平台最佳实践
北京 Android开发技术进阶
某新能源领域企业 Android开发技术
某航天公司 Android、IOS应用软件开发
阿尔卡特 Linux内核驱动
艾默生 嵌入式软件架构设计
西门子 嵌入式架构设计
更多...