zoukankan      html  css  js  c++  java
  • Android仿微信朋友圈图片查看器

            转载请注明出处:http://blog.csdn.net/allen315410/article/details/40264551

            看博文之前,希望大家先打开自己的微信点到朋友圈中去,细致观察是不是发现朋友圈里的有个“九宫格”的图片区域,点击图片又会跳到图片的具体查看页面,而且支持图片的滑动和缩放?这个功能是不是非经常常使用呢?!那么我今天正好做了这个Demo。以下为大家解说一下。首先依照惯例先看一下效果图吧,尤其不会录制gif动画(哎~没办法,模拟器不支持多点触控。刚好我的手机又没有Root,不能录屏,悲催啊,大家见谅,想要看真实效果的话,烦请移到博文最下方。点击下载源代码,执行后再看效果哈~~),这里先就拿几张静态的图片顶替一下好了。

    见谅!

    主页ListView的效果:                                  点击九宫格图片跳转到大图                                     多点触控,缩放图片

            

            效果嘛,将就着看吧!

    实在看不明确就想想微信朋友圈,或者拖到下方。点击下载源代码!这里,首先分析一下主界面吧。布局都是非常easy的。主界面只就是一个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);
    	}
    
    }
    
            接下来看看ListView上面Item的实体是什么样的数据结构,这就显得很easy了。

    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);
    	}
    }
           定义这个Application之后。须要在清单文件里配置一下,在Manifest.xml中的Application节点上加入:

    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" />
           再看看上面的Item上数据。里面有个GridView,显然这个GridView也是须要做数据适配的,这个数据反应的是从网络载入图片。比較简单,看代码NoScrollGridAdapter.java

          ......
    @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;
    	}
         ......
             这样,全部的数据适配就做好了,接下来就须要做图片查看器了,当我们点击ListView上Item里的“九宫格”——NoScrollGridView的某张图片的时候,须要把这个图片的url传给一个图片查看器,图片查看器里会依据传递进来的url去网络载入这张图片,那么事实上图片查看器就是一个新的单独的Activity。这个Activity会包括一个ViewPager。用来管理多张图片的查看。

    image_detail_pager.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" >
    
        <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>
    HackyViewPager.java
    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;
    		}
    	}
    
    }
    
    ImagePagerActivity.java
    /**
     * 图片查看器
     */
    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>
    ImageDetailFragment.java

    /**
     * 单张图片显示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);
                }
            }
        }
            以上仅仅是PhotoView的部分源代码。一目了然的发现它的实现也是基于Matrix的。时间与篇幅的局限性。大家须要更好的了解PhotoView的实现的话,就下载它的源代码查看吧。要理解大神的想法是须要一些扎实的基础,关于PhotoView的详细实现细节,我也弄不太明确,可能是我对Matrix了解的不深刻吧,希望以后加强学习,也希望以后跟你们交流学习,共同进步!


    源代码请在这里下载


  • 相关阅读:
    Kinect关于PlayerIndex和SkeletonId之间的关系。
    记一次“应用程序之间的通信”过程(1/2)
    C# p-Inovke C++动态链接库
    我写了本破书-swift语言实战晋级
    swift语言实战晋级-第9章 游戏实战-跑酷熊猫-9-10 移除平台与视差滚动
    swift语言实战晋级-第9章 游戏实战-跑酷熊猫-7-8 移动平台的算法
    Swift语言实战晋级-第9章 游戏实战-跑酷熊猫-5-6 踩踏平台是怎么炼成的
    Swift语言实战晋级-第9章 游戏实战-跑酷熊猫-4 熊猫的跳和打滚
    Swift语言实战晋级-第9章 游戏实战-跑酷熊猫-3 显示一个动态的熊猫
    Swift语言实战晋级-第9章 游戏实战-跑酷熊猫-2 创建熊猫类
  • 原文地址:https://www.cnblogs.com/wzjhoutai/p/6879993.html
Copyright © 2011-2022 走看看