转载请注明出处:http://blog.csdn.net/allen315410/article/details/40264551
看博文之前,希望大家先打开自己的微信点到朋友圈中去,仔细观察是不是发现朋友圈里的有个“九宫格”的图片区域,点击图片又会跳到图片的详细查看页面,并且支持图片的滑动和缩放?这个功能是不是很常用呢?!那么我今天正好做了这个Demo,下面为大家讲解一下。首先按照惯例先看一下效果图吧,尤其不会录制gif动画(哎~没办法,模拟器不支持多点触控,刚好我的手机又没有Root,不能录屏,悲催啊,大家见谅,想要看真实效果的话,烦请移到博文最下方,点击下载源码,运行后再看效果哈~~),这里先就拿几张静态的图片顶替一下好了。见谅!
主页ListView的效果: 点击九宫格图片跳转到大图 多点触控,缩放图片
效果嘛,将就着看吧!实在看不明白就想想微信朋友圈,或者拖到下方,点击下载源码!这里,首先分析一下主界面吧,布局都是很简单的,主界面仅仅就是一个ListView的控件,ListView的Item上值得注意的是,Item上包含了一个GridView,这个GridView呗用作实现“九宫格”的效果,主界面布局就是一个ListView,这里不说了,我们先来看看ListView的Item的布局吧,以下是item_list.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:paddingBottom="5dp"
- android:paddingTop="5dp" >
- <ImageView
- android:id="@+id/iv_avatar"
- android:layout_width="50dp"
- android:layout_height="50dp"
- android:background="@drawable/ic_launcher"
- android:scaleType="centerCrop" />
- <TextView
- android:id="@+id/tv_title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginLeft="5dp"
- android:layout_toRightOf="@id/iv_avatar"
- android:text="爷,今天心情好!"
- android:textSize="16sp" />
- <TextView
- android:id="@+id/tv_content"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@+id/tv_title"
- android:layout_marginLeft="5dp"
- android:layout_marginTop="3dp"
- android:layout_toRightOf="@id/iv_avatar"
- android:text="今天又是雾霾!"
- android:textSize="16sp" />
- <com.example.imagedemo.NoScrollGridView
- android:id="@+id/gridview"
- android:layout_width="220dp"
- android:layout_height="wrap_content"
- android:layout_below="@id/tv_content"
- android:layout_marginLeft="5dp"
- android:layout_marginTop="3dp"
- android:layout_toRightOf="@id/iv_avatar"
- android:columnWidth="70dp"
- android:gravity="center"
- android:horizontalSpacing="2.5dp"
- android:numColumns="3"
- android:stretchMode="columnWidth"
- android:verticalSpacing="2.5dp" />
- </RelativeLayout>
好了,大家看到了,布局也是极其简单的,但是有个问题就是ListView嵌套进了GridView,那么就会出现一个问题,导致GridView显示的不全,那么该怎么解决这个问题呢?其实也简单,就是重写一个GridView,测量一下GridView的高度,再设置上去。具体解决方案请看上篇博文ListView嵌套GridView显示不全解决方法或者源码,如下NoScrollGridView.Java
- package com.example.imagedemo;
- import android.content.Context;
- import android.util.AttributeSet;
- import android.widget.GridView;
- /**
- * 自定义的“九宫格”——用在显示帖子详情的图片集合 解决的问题:GridView显示不全,只显示了一行的图片,比较奇怪,尝试重写GridView来解决
- *
- * @author lichao
- * @since 2014-10-16 16:41
- *
- */
- public class NoScrollGridView extends GridView {
- public NoScrollGridView(Context context) {
- super(context);
- // TODO Auto-generated constructor stub
- }
- public NoScrollGridView(Context context, AttributeSet attrs) {
- super(context, attrs);
- // TODO Auto-generated constructor stub
- }
- public NoScrollGridView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- // TODO Auto-generated constructor stub
- }
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- // TODO Auto-generated method stub
- int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
- MeasureSpec.AT_MOST);
- super.onMeasure(widthMeasureSpec, expandSpec);
- }
- }
- public class ItemEntity {
- private String avatar; // 用户头像URL
- private String title; // 标题
- private String content; // 内容
- private ArrayList<String> imageUrls; // 九宫格图片的URL集合
- public ItemEntity(String avatar, String title, String content,
- ArrayList<String> imageUrls) {
- super();
- this.avatar = avatar;
- this.title = title;
- this.content = content;
- this.imageUrls = imageUrls;
- }
- ...
- }
好了,有了ListView,那么不可避免的就是做Item上的数据适配了。继承一个BaseAdapter,代码如下,都比较简单:
- /**
- * 首页ListView的数据适配器
- *
- * @author Administrator
- *
- */
- public class ListItemAdapter extends BaseAdapter {
- private Context mContext;
- private ArrayList<ItemEntity> items;
- public ListItemAdapter(Context ctx, ArrayList<ItemEntity> items) {
- this.mContext = ctx;
- this.items = items;
- }
- @Override
- public int getCount() {
- return items == null ? 0 : items.size();
- }
- @Override
- public Object getItem(int position) {
- return items.get(position);
- }
- @Override
- public long getItemId(int position) {
- return position;
- }
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- ViewHolder holder;
- if (convertView == null) {
- holder = new ViewHolder();
- convertView = View.inflate(mContext, R.layout.item_list, null);
- holder.iv_avatar = (ImageView) convertView
- .findViewById(R.id.iv_avatar);
- holder.tv_title = (TextView) convertView
- .findViewById(R.id.tv_title);
- holder.tv_content = (TextView) convertView
- .findViewById(R.id.tv_content);
- holder.gridview = (NoScrollGridView) convertView
- .findViewById(R.id.gridview);
- convertView.setTag(holder);
- } else {
- holder = (ViewHolder) convertView.getTag();
- }
- ItemEntity itemEntity = items.get(position);
- holder.tv_title.setText(itemEntity.getTitle());
- holder.tv_content.setText(itemEntity.getContent());
- // 使用ImageLoader加载网络图片
- DisplayImageOptions options = new DisplayImageOptions.Builder()//
- .showImageOnLoading(R.drawable.ic_launcher) // 加载中显示的默认图片
- .showImageOnFail(R.drawable.ic_launcher) // 设置加载失败的默认图片
- .cacheInMemory(true) // 内存缓存
- .cacheOnDisk(true) // sdcard缓存
- .bitmapConfig(Config.RGB_565)// 设置最低配置
- .build();//
- ImageLoader.getInstance().displayImage(itemEntity.getAvatar(),
- holder.iv_avatar, options);
- final ArrayList<String> imageUrls = itemEntity.getImageUrls();
- if (imageUrls == null || imageUrls.size() == 0) { // 没有图片资源就隐藏GridView
- holder.gridview.setVisibility(View.GONE);
- } else {
- holder.gridview.setAdapter(new NoScrollGridAdapter(mContext,
- imageUrls));
- }
- // 点击回帖九宫格,查看大图
- holder.gridview.setOnItemClickListener(new OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> parent, View view,
- int position, long id) {
- // TODO Auto-generated method stub
- imageBrower(position, imageUrls);
- }
- });
- return convertView;
- }
- /**
- * 打开图片查看器
- *
- * @param position
- * @param urls2
- */
- protected void imageBrower(int position, ArrayList<String> urls2) {
- Intent intent = new Intent(mContext, ImagePagerActivity.class);
- // 图片url,为了演示这里使用常量,一般从数据库中或网络中获取
- intent.putExtra(ImagePagerActivity.EXTRA_IMAGE_URLS, urls2);
- intent.putExtra(ImagePagerActivity.EXTRA_IMAGE_INDEX, position);
- mContext.startActivity(intent);
- }
- /**
- * listview组件复用,防止“卡顿”
- *
- * @author Administrator
- *
- */
- class ViewHolder {
- private ImageView iv_avatar;
- private TextView tv_title;
- private TextView tv_content;
- private NoScrollGridView gridview;
- }
- }
这里有需要解释的地方了,看看listview上的图片处理,由于图片都是从网络获取的,为了避免图片过多造成OOM,那么这里加载图片的时候必不可少的需要做内存优化,图片的优化方式有很多,我这里采取了最简单最直接得方式,使用了开源的ImageLoader这个图片加载框架,这个框架简直是太优秀了,减少了开发者一系列不必要而且时常会出现的麻烦,关于ImageLoader并不是本篇博文需要讲解的知识,关于ImageLoader,欢迎在GitHub主页上下载,地址是https://github.com/nostra13/Android-Universal-Image-Loader,既然使用了ImageLoader这个框架,就不得不在程序上做一些初始化的操作,首先需要自定义一个全局的上下文Application类,将ImageLoader的相关属性初始化上去,直接看代码好了,见名知意:MyApplication.java
- public class MyApplication extends Application {
- @Override
- public void onCreate() {
- super.onCreate();
- DisplayImageOptions defaultOptions = new DisplayImageOptions.Builder() //
- .showImageForEmptyUri(R.drawable.ic_launcher) //
- .showImageOnFail(R.drawable.ic_launcher) //
- .cacheInMemory(true) //
- .cacheOnDisk(true) //
- .build();//
- ImageLoaderConfiguration config = new ImageLoaderConfiguration//
- .Builder(getApplicationContext())//
- .defaultDisplayImageOptions(defaultOptions)//
- .discCacheSize(50 * 1024 * 1024)//
- .discCacheFileCount(100)// 缓存一百张图片
- .writeDebugLogs()//
- .build();//
- ImageLoader.getInstance().init(config);
- }
- }
- android:name="com.example.imagedemo.MyApplication"
此外由于ImageLoader是网络获取图片,又需要本地sdcard缓存图片,所以需要加上一下的权限,这是Imageloader标准权限:
- <uses-permission android:name="android.permission.INTERNET" />
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
- ......
- Override
- public View getView(int position, View convertView, ViewGroup parent) {
- View view = View.inflate(ctx, R.layout.item_gridview, null);
- ImageView imageView = (ImageView) view.findViewById(R.id.iv_image);
- DisplayImageOptions options = new DisplayImageOptions.Builder()//
- .cacheInMemory(true)//
- .cacheOnDisk(true)//
- .bitmapConfig(Config.RGB_565)//
- .build();
- ImageLoader.getInstance().displayImage(imageUrls.get(position),
- imageView, options);
- return view;
- }
- ......
- <?xml version="1.0" encoding="utf-8"?>
- <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent" >
- <com.example.imagedemo.HackyViewPager
- android:id="@+id/pager"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="@android:color/black" />
- <TextView
- android:id="@+id/indicator"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="bottom"
- android:background="@android:color/transparent"
- android:gravity="center"
- android:text="@string/viewpager_indicator"
- android:textColor="@android:color/white"
- android:textSize="18sp" />
- </FrameLayout>
- public class HackyViewPager extends ViewPager {
- private static final String TAG = "HackyViewPager";
- public HackyViewPager(Context context) {
- super(context);
- }
- public HackyViewPager(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- try {
- return super.onInterceptTouchEvent(ev);
- } catch (IllegalArgumentException e) {
- // 不理会
- Log.e(TAG, "hacky viewpager error1");
- return false;
- } catch (ArrayIndexOutOfBoundsException e) {
- // 不理会
- Log.e(TAG, "hacky viewpager error2");
- return false;
- }
- }
- }
- /**
- * 图片查看器
- */
- public class ImagePagerActivity extends FragmentActivity {
- private static final String STATE_POSITION = "STATE_POSITION";
- public static final String EXTRA_IMAGE_INDEX = "image_index";
- public static final String EXTRA_IMAGE_URLS = "image_urls";
- private HackyViewPager mPager;
- private int pagerPosition;
- private TextView indicator;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.image_detail_pager);
- pagerPosition = getIntent().getIntExtra(EXTRA_IMAGE_INDEX, 0);
- ArrayList<String> urls = getIntent().getStringArrayListExtra(
- EXTRA_IMAGE_URLS);
- mPager = (HackyViewPager) findViewById(R.id.pager);
- ImagePagerAdapter mAdapter = new ImagePagerAdapter(
- getSupportFragmentManager(), urls);
- mPager.setAdapter(mAdapter);
- indicator = (TextView) findViewById(R.id.indicator);
- CharSequence text = getString(R.string.viewpager_indicator, 1, mPager
- .getAdapter().getCount());
- indicator.setText(text);
- // 更新下标
- mPager.setOnPageChangeListener(new OnPageChangeListener() {
- @Override
- public void onPageScrollStateChanged(int arg0) {
- }
- @Override
- public void onPageScrolled(int arg0, float arg1, int arg2) {
- }
- @Override
- public void onPageSelected(int arg0) {
- CharSequence text = getString(R.string.viewpager_indicator,
- arg0 + 1, mPager.getAdapter().getCount());
- indicator.setText(text);
- }
- });
- if (savedInstanceState != null) {
- pagerPosition = savedInstanceState.getInt(STATE_POSITION);
- }
- mPager.setCurrentItem(pagerPosition);
- }
- @Override
- public void onSaveInstanceState(Bundle outState) {
- outState.putInt(STATE_POSITION, mPager.getCurrentItem());
- }
- private class ImagePagerAdapter extends FragmentStatePagerAdapter {
- public ArrayList<String> fileList;
- public ImagePagerAdapter(FragmentManager fm, ArrayList<String> fileList) {
- super(fm);
- this.fileList = fileList;
- }
- @Override
- public int getCount() {
- return fileList == null ? 0 : fileList.size();
- }
- @Override
- public Fragment getItem(int position) {
- String url = fileList.get(position);
- return ImageDetailFragment.newInstance(url);
- }
- }
- }
已知图片查看的界面是继承自FragmentActivity的,所以支持显示的界面必须需要Fragment来实现,那么就自定义个Frangment吧,用这个Fragment来从url中获取图片资源,显示图片。image_detail_fragment.xml
- <?xml version="1.0" encoding="utf-8"?>
- <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="@android:color/black" >
- <ImageView
- android:id="@+id/image"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:adjustViewBounds="true"
- android:contentDescription="@string/app_name"
- android:scaleType="centerCrop" />
- <ProgressBar
- android:id="@+id/loading"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:visibility="gone" />
- </FrameLayout>
- /**
- * 单张图片显示Fragment
- */
- public class ImageDetailFragment extends Fragment {
- private String mImageUrl;
- private ImageView mImageView;
- private ProgressBar progressBar;
- private PhotoViewAttacher mAttacher;
- public static ImageDetailFragment newInstance(String imageUrl) {
- final ImageDetailFragment f = new ImageDetailFragment();
- final Bundle args = new Bundle();
- args.putString("url", imageUrl);
- f.setArguments(args);
- return f;
- }
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mImageUrl = getArguments() != null ? getArguments().getString("url")
- : null;
- }
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- final View v = inflater.inflate(R.layout.image_detail_fragment,
- container, false);
- mImageView = (ImageView) v.findViewById(R.id.image);
- mAttacher = new PhotoViewAttacher(mImageView);
- mAttacher.setOnPhotoTapListener(new OnPhotoTapListener() {
- @Override
- public void onPhotoTap(View arg0, float arg1, float arg2) {
- getActivity().finish();
- }
- });
- progressBar = (ProgressBar) v.findViewById(R.id.loading);
- return v;
- }
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
- ImageLoader.getInstance().displayImage(mImageUrl, mImageView,
- new SimpleImageLoadingListener() {
- @Override
- public void onLoadingStarted(String imageUri, View view) {
- progressBar.setVisibility(View.VISIBLE);
- }
- @Override
- public void onLoadingFailed(String imageUri, View view,
- FailReason failReason) {
- String message = null;
- switch (failReason.getType()) {
- case IO_ERROR:
- message = "下载错误";
- break;
- case DECODING_ERROR:
- message = "图片无法显示";
- break;
- case NETWORK_DENIED:
- message = "网络有问题,无法下载";
- break;
- case OUT_OF_MEMORY:
- message = "图片太大无法显示";
- break;
- case UNKNOWN:
- message = "未知的错误";
- break;
- }
- Toast.makeText(getActivity(), message,
- Toast.LENGTH_SHORT).show();
- progressBar.setVisibility(View.GONE);
- }
- @Override
- public void onLoadingComplete(String imageUri, View view,
- Bitmap loadedImage) {
- progressBar.setVisibility(View.GONE);
- mAttacher.update();
- }
- });
- }
- }
写到这里,此篇博文也宣告结束了。需要提出的是,我这里的图片查看器实现的图片的缩放效果使用的是开源组件PhotoView,关于PhotoView的github项目地址在这里,https://github.com/chrisbanes/PhotoView 需要点进去这个项目的网址,去下载源码,将源码全部拷贝到项目中来,使用也是相当方便的,demo如下:
- ImageView mImageView;
- PhotoViewAttacher mAttacher;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- // Any implementation of ImageView can be used!
- mImageView = (ImageView) findViewById(R.id.iv_photo);
- // Set the Drawable displayed
- Drawable bitmap = getResources().getDrawable(R.drawable.wallpaper);
- mImageView.setImageDrawable(bitmap);
- // Attach a PhotoViewAttacher, which takes care of all of the zooming functionality.
- mAttacher = new PhotoViewAttacher(mImageView);
- }
- // If you later call mImageView.setImageDrawable/setImageBitmap/setImageResource/etc then you just need to call
- attacher.update();
刚开始这个图片查看器是我自己自定义View来实现的,其实需要实现图片的手势识别+多点触控+缩放,是可以使用矩阵Matrix来实现的,只不过这样显得特别的麻烦不说,而且极易出现BUG,这对于某些“急功近利”的项目来说,是个不好的兆头。所以,我这里摒弃了我用Matrix自定义的效果,改用github大牛为我们写好的开源组件,这样效率就上去了,大家也可以用Matrix自己去实现一下图片的多点触摸缩放的效果,关于Matrix的学习,请参加我以前的博文,Android自定义控件——3D画廊和图像矩阵。其实关于android上的图片缩放真没什么其它的方式,唯一能使用的还是Matrix这个类,不信先来瞧瞧Github大牛写的开源组件PhotoView是怎么实现的,查看以下部分源码:
- // These are set so we don't keep allocating them on the heap
- private final Matrix mBaseMatrix = new Matrix();
- private final Matrix mDrawMatrix = new Matrix();
- private final Matrix mSuppMatrix = new Matrix();
- private final RectF mDisplayRect = new RectF();
- private final float[] mMatrixValues = new float[9];
- /**
- * Set's the ImageView's ScaleType to Matrix.
- */
- private static void setImageViewScaleTypeMatrix(ImageView imageView) {
- /**
- * PhotoView sets it's own ScaleType to Matrix, then diverts all calls
- * setScaleType to this.setScaleType automatically.
- */
- if (null != imageView && !(imageView instanceof IPhotoView)) {
- if (!ScaleType.MATRIX.equals(imageView.getScaleType())) {
- imageView.setScaleType(ScaleType.MATRIX);
- }
- }
- }