~转载请注明:http://blog.csdn.net/u013015161/article/details/46640273

介绍

最近写了一个Library, 用于实现在Android设备上对大图的浏览。已经实现的功能有:
1、移动、缩放图片
2、双击快速放大或缩小图片
3、单击退出浏览
4、左右滑动切换图片。
目前还只实现了展示SD卡里图片的功能,后续应该补完,使其可以展示网络图片等。
代码已经在Github上开源, 地址为:
https://github.com/lankton/lanimagebrowser

展示:
图片切换
【Android】一个浏览图片的Android库的实现,可以移动、缩放图片以及滑动切换_第1张图片

图片缩放
【Android】一个浏览图片的Android库的实现,可以移动、缩放图片以及滑动切换_第2张图片

实现

实现的思路很简单。图片的缩放、移动等操作通过自定义ImageView实现,这些自定义ImageView通过Fragment来展现。同时,这些Fragment被绑定到ViewPager上,从而实现对图片的切换。下面简单讲一下几个比较关键的地方。
1. 自定义ImageView
主要重写了OnTouchEvent,来监听各种手势事件。同时重写了OnMeasure和OnLayout,来初始化图片在ImageView的显示。直接上代码吧。

package com.lankton.imagebrowser;import java.util.Timer;import java.util.TimerTask;import android.app.Activity;import android.content.Context;import android.graphics.Matrix;import android.graphics.PointF;import android.util.AttributeSet;import android.view.MotionEvent;import android.widget.ImageView;public class BrowserImageView extends ImageView {    Context context;    float originDistance;    float curDistance;    float scale; //在上次基础上缩放    float curScale = 1;    float beginZoomScale; //开始缩放时的scale    Matrix matrix = new Matrix();    Matrix savedMatrix = new Matrix();    PointF curPoint = new PointF();    PointF lastPoint = new PointF();    public BitmapSize bitmapSize;    private Timer closeTimer;    private boolean isClose;    private final float BOUNDS = 30;    private float originX;    private float originY;    float smallScale;    float bigScale;    boolean isToBig = true;    public BrowserImageView(Context context, AttributeSet attrs) {        super(context, attrs);        this.context = context;//        bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.sb);          // TODO Auto-generated constructor stub    }    public BrowserImageView(Context c)    {        super(c);        this.context = c;    }    public void setBitmapSize(BitmapSize b)    {        this.bitmapSize = b;    }    @Override    public boolean onTouchEvent(MotionEvent event) {        // TODO Auto-generated method stub        switch(event.getAction() & MotionEvent.ACTION_MASK)        {        case MotionEvent.ACTION_DOWN:            curPoint.x = event.getX();            curPoint.y = event.getY();            savedMatrix.set(matrix);              isClose = true;            originX = curPoint.x;            originY = curPoint.y;            break;        case MotionEvent.ACTION_POINTER_DOWN:            isClose = false;            originDistance = getDistance(event.getX(0), event.getY(0),                    event.getX(1), event.getY(1));            beginZoomScale = curScale;            break;        case MotionEvent.ACTION_MOVE:            if(isOutBounds(originX, originY, event.getX(0), event.getY(0)))            {                isClose = false;            }            if(event.getPointerCount() == 2)            {                curDistance = getDistance(event.getX(0), event.getY(0),                        event.getX(1), event.getY(1));                scale = curDistance / originDistance;                curScale = beginZoomScale * scale;                matrix.set(savedMatrix);                 matrix.postScale(scale,  scale                        , (event.getX(0) + event.getX(1))/2                        , (event.getY(0) + event.getY(1))/2);                this.setImageMatrix(matrix);            }            else if(event.getPointerCount() == 1)            {                lastPoint.x = curPoint.x;                lastPoint.y = curPoint.y;                curPoint.x = event.getX();                curPoint.y = event.getY();                matrix.postTranslate(curPoint.x - lastPoint.x, curPoint.y - lastPoint.y);                this.setImageMatrix(matrix);             }            break;        case MotionEvent.ACTION_UP:            if(isClose)            {                if(null == closeTimer)                {                    closeTimer = new Timer();                    TimerTask task = new TimerTask(){                        @Override                        public void run() {                            // TODO Auto-generated method stub                            ((Activity) context).finish();                        }                    };                     closeTimer.schedule(task, 500);                }                else                {//double click                    closeTimer.cancel();                    closeTimer = null;                    if(isToBig)                    {                        matrix.postScale(bigScale / curScale , bigScale / curScale, event.getX(), event.getY());                        curScale = bigScale;                        this.setImageMatrix(matrix);                         isToBig = false;                    }                    else                    {                        matrix.postScale(smallScale / curScale, smallScale / curScale, event.getX(), event.getY());                        curScale = smallScale;                        this.setImageMatrix(matrix);                         isToBig = true;                    }                }            }            break;        case MotionEvent.ACTION_POINTER_UP:            if(1 == event.getActionIndex())            {                curPoint.x = event.getX(0);                curPoint.y = event.getY(0);            }            else            {                curPoint.x = event.getX(1);                curPoint.y = event.getY(1);            }            savedMatrix.set(matrix);            break;        default:            break;        }        return true;    }    /*获得两点间距离*/    public float getDistance(float x1, float y1, float x2, float y2)    {        float disX = x1 - x2;        float disY = y1 - y2;        return (float)Math.sqrt(disX * disX + disY * disY);    }    /*设置图片以合适大小居中*/    public void center()    {        int viewWidth = this.getMeasuredWidth();        int viewHeight = this.getMeasuredHeight();        int bitmapWidth = bitmapSize.width;        int bitmapHeight = bitmapSize.height;        float scale = 1;        //先居中        matrix.setTranslate((viewWidth - bitmapWidth)/2f, (viewHeight - bitmapHeight)/2f);        //图片宽高有大于容器, 则需要再进行一次缩放处理        if(bitmapWidth > viewWidth || bitmapHeight > viewHeight)        {            if((float)bitmapWidth / bitmapHeight > (float)viewWidth / viewHeight)            {                //宽、高比大于容器,以宽占满容器宽度为准,进行缩放                scale = (float)viewWidth / bitmapWidth;                               matrix.postScale(scale, scale, (float)viewWidth / 2, (float)viewHeight / 2);            }            else            {                //高、宽比大于容器, 以高占满容器高度为准,进行缩放                scale = (float)viewHeight / bitmapHeight;                matrix.postScale(scale, scale, (float)viewWidth / 2, (float)viewHeight / 2);            }        }        smallScale = scale;        bigScale = smallScale * 2;        this.setImageMatrix(matrix);    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        // TODO Auto-generated method stub        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        refresh();//        center();    }    @Override      protected void onLayout(boolean changed, int l, int t, int r, int b) {          if(null == bitmapSize)        {            return;        }        center();    }      /*刷新matrix*/    public void refresh()    {        matrix.reset();        this.setImageMatrix(matrix);    }    public class BitmapSize    {        public int width;        public int height;        public BitmapSize(int width, int height)        {            this.width = width;            this.height = height;        }    }    /*手指在屏幕上移动超过范围才被判定为滑动,否则影响点击事件的判断*/    public boolean isOutBounds(float x1, float y1, float x2, float y2 )    {        return Math.abs(x2 - x1) *  Math.abs(x2 - x1)                 +  Math.abs(y2 - y1) * Math.abs(y2 - y1) > BOUNDS * BOUNDS;     }}

可以看到,图片的位移及大小变换是通过修改matrix实现的,所以使用时该自定义View的scaleType被设为“matrix”。
2. Fragment编写
这个,也还是直接上代码吧。。

package com.lankton.imagebrowser;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.util.Timer;import java.util.TimerTask;import com.lankton.imagebrowser.BrowserImageView.BitmapSize;import android.R;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.os.AsyncTask;import android.os.Bundle;import android.support.v4.app.Fragment;import android.support.v4.util.LruCache;import android.util.Log;import android.view.LayoutInflater;import android.view.View;import android.view.View.OnClickListener;import android.view.ViewGroup;import android.widget.ImageView.ScaleType;import android.widget.RelativeLayout;import android.widget.TextView;import android.widget.Toast;public class IFragment extends Fragment{    public BrowserImageView img;    private String path;    private int pos;    private Timer clickTimer;    LruCache cache;    @Override    public void onCreate(Bundle savedInstanceState) {        // TODO Auto-generated method stub        super.onCreate(savedInstanceState);    }    public IFragment()    {        super();    }    public IFragment(String path, int pos, LruCache cache) {        super();        this.path = path;        this.pos = pos;        this.cache = cache;    }    @Override    public View onCreateView(LayoutInflater inflater, ViewGroup container,            Bundle savedInstanceState) {        // TODO Auto-generated method stub        Log.v("browser", ""+pos+" onCreateview");        RelativeLayout relativeLayout = new RelativeLayout(this.getActivity());        relativeLayout.setBackgroundColor(this.getResources().getColor(R.color.black));        BrowserImageView bimg = new BrowserImageView(this.getActivity());        RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);        lp.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE);        bimg.setScaleType(ScaleType.MATRIX);        bimg.setClickable(true);        relativeLayout.addView(bimg, lp);        img = bimg;        img.setOnClickListener(new OnClickListener(){            @Override            public void onClick(View v) {                // TODO Auto-generated method stub                Toast.makeText(IFragment.this.getActivity(), "click", 3000).show();                if(null == clickTimer)                {                    clickTimer = new Timer();                    TimerTask task = new TimerTask(){                        @Override                        public void run() {                            // TODO Auto-generated method stub                            IFragment.this.getActivity().finish();                            Toast.makeText(IFragment.this.getActivity(), "close", 3000).show();                        }                    };                     clickTimer.schedule(task, 200);                }                else                {                    clickTimer.cancel();                    clickTimer = null;                }            }        });        TextView t = new TextView(this.getActivity());        t.setText("" + pos);        t.setTextSize(30);        t.setTextColor(this.getResources().getColor(R.color.white));        relativeLayout.addView(t,lp);        return relativeLayout;    }    @Override    public void onDestroy() {        // TODO Auto-generated method stub        Log.v("browser", ""+pos+" onDestroy");        super.onDestroy();    }    @Override    public void onPause() {        // TODO Auto-generated method stub          super.onPause();    }    @Override    public void onResume() {        // TODO Auto-generated method stub        Log.v("browser", ""+pos+" onResume");        super.onResume();        setBitmap();    }    @Override    public void onStop() {        // TODO Auto-generated method stub        Log.v("browser", ""+pos+" onStop");        super.onStop();    }    public void setBitmap() {        Bitmap bitmap = IFragment.this.getBitmapFromMemCache(path);        if(null == bitmap)        {            LoadBitmapTask task = new LoadBitmapTask();            task.execute();        } else {            BitmapSize bs = img.new BitmapSize(bitmap.getWidth(), bitmap.getHeight());            img.setBitmapSize(bs);            img.setImageBitmap(bitmap);        }    }    public void addBitmapToMemoryCache(String key, Bitmap bitmap) {        if (getBitmapFromMemCache(key) == null) {            cache.put(key, bitmap);        }    }    public Bitmap getBitmapFromMemCache(String key) {        return cache.get(key);    }    class LoadBitmapTask extends AsyncTask    {        @Override        protected Bitmap doInBackground(Void... params) {            // TODO Auto-generated method stub            Bitmap bitmap = null;            try {                FileInputStream fin = new FileInputStream(path);                final BitmapFactory.Options options = new BitmapFactory.Options();                options.inSampleSize = 2;                options.inJustDecodeBounds = false;                bitmap = BitmapFactory.decodeStream(fin, null, options);                IFragment.this.addBitmapToMemoryCache(path, bitmap);            } catch (FileNotFoundException e) {                // TODO Auto-generated catch block                e.printStackTrace();            }             return bitmap;        }        @Override        protected void onPostExecute(Bitmap result) {            // TODO Auto-generated method stub            super.onPostExecute(result);            BitmapSize bs = img.new BitmapSize(result.getWidth(), result.getHeight());            img.setBitmapSize(bs);            img.setImageBitmap(result);        }    }}

由于工程要被拿来当作library,可以看到在onCreateView通过代码生成自定义的BrowserImageView并被设置到布局里。BrowserImageView的scaleType被设置成“matrix”。加载时,通过AsyncTask异步加载图片。

  1. 图片缓存
    通过LruCache动态进行内存管理,否则很高概率出现OOM。缓存初始化放在了ViewPager的Adapter里:
package com.lankton.imagebrowser;import java.util.List;import android.graphics.Bitmap;import android.support.v4.app.Fragment;import android.support.v4.app.FragmentManager;import android.support.v4.app.FragmentPagerAdapter;import android.support.v4.util.LruCache;import android.util.Log;public class IPagerAdapter extends FragmentPagerAdapter{    List pathList;    LruCache cache;    public IPagerAdapter(FragmentManager fm, List pathList) {        super(fm);        this.pathList = pathList;        /*init LruCache*/        final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);Log.v("diskcache","mem : " + maxMemory);        final int cacheSize = 1 * 1024 * 1024;//maxMemory / 8;        cache = new LruCache(cacheSize);        // TODO Auto-generated constructor stub    }    @Override    public Fragment getItem(int position) {        // TODO Auto-generated method stub        IFragment f = new IFragment(pathList.get(position),position, cache);        return f;    }    @Override    public int getCount() {        // TODO Auto-generated method stub        return pathList.size();    }}

在之前介绍Fragment的代码里可以看到如何使用LruCache的,不再赘述了。

使用

本Library主要提供了一个PagerAdapter。使用时,让该工程作为Library被需要的工程引用即可。
使用时的代码如下:

@Override    protected void onCreate(Bundle savedInstanceState) {        // TODO Auto-generated method stub        super.onCreate(savedInstanceState);        this.setContentView(R.layout.lanactivity_browserimage);        viewPager = (ViewPager) this.findViewById(R.id.viewpager);        photos = this.getIntent().getStringArrayListExtra("photoList");        index = this.getIntent().getIntExtra("index", 0);        adapter = new IPagerAdapter(this.getSupportFragmentManager(), photos);        viewPager.setAdapter(adapter);        viewPager.setCurrentItem(index);    }

在你的布局文件里放置一个普通的ViewPager,然后使用类库提供的IPagerAdapter即可。需要传进Adapter的参数,即photos,是你本地文件的路径列表。之后该Activity就可以拿来进行图片浏览了。

就先介绍到这里吧, 这个Library目前还有不少需要改进和提升的地方,请多多指教。

最后在申明一次开源地址, 代码都可以从这里获取:
https://github.com/lankton/lanimagebrowser

更新

解决viewpager和imageview的滑动冲突 2015 6 28

之前版本,想直接左右拖动图片时(eg 图片放大状态,想查看未显示的部分),会直接出发viewpager的翻页事件。
解决方案:手指在imageview上move时,根据条件判断是否应该禁止viewpager的滑动事件。参考链接:requestDisallowInterceptTouchEvent
参考里viewpager直接传递进子view, 其实不用,可以直接通过getParent()获得。同时本library的情况要分别考虑左划和右划。代码如下:

Rect rectTemp = this.getDrawable().getBounds();                 matrix.getValues(values);                int leftPos = (int)values[2];                int rightPos = (int)(values[2]+rectTemp.width()*values[0]);                  lastPoint.x = curPoint.x;                lastPoint.y = curPoint.y;                curPoint.x = event.getX();                curPoint.y = event.getY();                if(leftPos < 0 && curPoint.x > lastPoint.x || rightPos > viewWidth && curPoint.x < lastPoint.x)                {// 图片左边未显示完全时禁止向右划,向左划同理                    this.getParent().requestDisallowInterceptTouchEvent(true);                }                else                 {                    this.getParent().requestDisallowInterceptTouchEvent(false);                    return true;                }

已同步至git。

更多相关文章

  1. 【Android】图片(文件)上传的请求分析结构
  2. Android通过WebService实现图片的上传和下载(一)
  3. Android 中图片压缩分析(上)
  4. Android 的 那些 秘密代码
  5. Android 常用图片框架对比
  6. Android超炫图片浏览器代码
  7. [eclipse]android开发如何查看源代码文件(android source)
  8. Android之A面试题④应用程序内部启动Activity过程(startActivity)

随机推荐

  1. android 开发框架
  2. ADB的使用
  3. Android(安卓)Studio 使用小技巧和快捷键
  4. lua学习笔记 0 android嵌入Lua
  5. 升级android sdk到5.0时,创建项目出现错误
  6. Android* 操作系统上的应用程序远程调试
  7. Android设计模式系列--工厂方法模式
  8. 开始学习android开发
  9. [Android]实现静默安装APK的两种方法
  10. android 自定义动画1