安卓canvas实现拖动显示大背景图片

来源:互联网 发布:西西网络图绘制软件 编辑:程序博客网 时间:2024/06/06 02:07

我是个自学安卓开发的菜鸟,糊里糊涂地参加了一个安卓应用开发的项目。项目中有这样的需求:
一张很大的背景图,用户通过拖动来查看背景图的不同区域;背景图上还有小图片,也需要在背景图滑动的同时跟着一起动。

我一开始想,这还不好做嘛,直接把用ImageView控件显示图片,加一个触摸监听事件,根据触摸距离调整控件位置不就得了?然而第一个问题出现了:无法按照原大小显示图片。
这是我期望的效果
↑我期望的效果
这是实际的效果
↑实际的效果

按照网上的说法,想要以原大小显示图片,只要在该ImageView控件里加这么一行:android:scaleType="matrix"
我加上之后,的确能够按照原大小显示图片了,然而我为ImageView添加了OnTouchListener之后,改变ImageView控件的位置,发现一开始未显示的图片,在滑动时也不能显示。
如图
↑左图为未拖动的显示,右图为拖动之后的显示,可以看出,超出屏幕的图片在拖动控件之后并没有如期出现

当时我还百思不得其解,回想起来,这其实是因为ImageView是屏幕内的控件,即使把它设为

android:layout_width="wrap_content"android:layout_height="wrap_content"

ImageView的大小也不会超出屏幕的。所以在其内容超出屏幕大小时,ImageView的大小也依然是屏幕大小,滑动时是把ImageView移来移去,自然也不能指望着ImageView控件范围之外呈现出ImageView的内容。
那么解决方法也就出来了:
我不该移动ImageView的位置,而是应考虑滑动时需要把图片的哪一部分显示在ImageView中
如图
↑蓝框表示ImageView控件,右边为我想显示的大图,被填充的区域为大图需要显示在ImageView中的部分

但是默认的ImageView似乎并没有这个功能,所以我决定试着写一个能实现上述功能的自定义View控件类。
这个控件的功能呢,就是被拖动时,计算在原图中需要截取的图片的起始位置。
你问我为什么不需要知道宽和高?因为宽和高就是获取到的设备屏幕的宽和高呀。
你问我怎么获取设备的宽和高?其实很简单,我也是从网上查的,贴在这好啦:

DisplayMetrics dm=new DisplayMetrics();         super.getWindowManager().getDefaultDisplay().getMetrics(dm);//dm.widthPixels和dm.heightPixels就是屏幕的宽和高啦//正如其变量名所示,是以像素为单位的 

那么获取到显示图片的宽和高,该如何计算从哪个位置开始截取图片呢?
这里我们就需要重写View类的
onTouchEvent(MotionEvent event)函数

//自定义View类的成员变量://lastX、lastY:上一个触摸点的位置坐标//startX、startY:截取图片相对于原图的起始坐标//bmWidth、bmHeight:原图的宽和高    @Override    public boolean onTouchEvent(MotionEvent event) {        // TODO Auto-generated method stub        switch (event.getAction()) {        //手刚按下时,记录起始点的位置        case MotionEvent.ACTION_DOWN:            lastX = (int) event.getRawX();            lastY = (int) event.getRawY();            break;        //手开始移动,获取此时手的位置,与起始位置做差        //得到移动位置和起始位置的差值dx、dy        case MotionEvent.ACTION_MOVE:            int dx = (int) event.getRawX() - lastX;            int dy = (int) event.getRawY() - lastY;            //此处注意是减去dx和dy            //因为手的拖动和图片截取的位置是个相对运动            //如:手从左往右,触摸点的x坐标在变大            //而截取图片的起始点x坐标在变小            //(新截取的图在原来截取的图的左边儿)            startX-=dx;            startY-=dy;            //以下俩if..else是用来防止拖动到原图以外的区域            if(startX<0)            {                startX=0;            }            else if(startX+dm.widthPixels>bmWidth)            {                startX=bmWidth-dm.widthPixels;            }            if(startY<0)            {                startY=0;            }            else if(startY+dm.heightPixels>bmHeight)            {                startY=bmHeight-dm.heightPixels;            }            //再次获取当前的坐标作为下一次计算的起始值            lastX=(int) event.getRawX();            lastY=(int) event.getRawY();            //调用onDraw函数            invalidate();            break;        case MotionEvent.ACTION_UP:            break;        }        return true;    }

这里得到的startXstartY就是截取图片相对于原图左下角的坐标啦。得到之后,得把它画出来啊,所以我还得把onDraw(Canvas canvas)函数也重写一下:

//成员变量://bitmap:就是需要显示的原图啦//act:我放置自定义View控件的某个Activity@Override    protected void onDraw(Canvas canvas) {        // TODO Auto-generated method stub        super.onDraw(canvas);           //获取原图上需要截取的区域          Rect src=new Rect(startX,startY,startX+dm.widthPixels,startY+dm.heightPixels);        //把需要在屏幕上绘制的区域框出来        Rect dst=new Rect(0,0,dm.widthPixels,dm.heightPixels);        //在屏幕指定区域画出截取区域        canvas.drawBitmap(bitmap, src, dst, null);         //大图上面有小图img_me,也需要跟着一起动        //因此在这里获取到activity里的img_me,改变其坐标        act.img_me.setTranslationX(-startX+1200);        act.img_me.setTranslationY(-startY+600);            }

这样,我自定义的View控件的核心功能就完成了,在触摸回调函数里计算截取区域的位置,在绘制回调函数里画出截取区域。
然后,就可以在Activity里放入这个控件以显示了~

<!--这是该Activity的布局文件里需要添加的--><com.test.my.MyView       android:id="@+id/myView"       android:layout_width="match_parent"       android:layout_height="match_parent"/>

但是,运行时报错了,会是什么地方出错了呢?
嗯~自定义View一定不要忘记,要重写其构造函数哦!

//1个参数的构造函数//如果该View是用java代码创建的,那么就会调用此构造函数//这里其实没用到这个构造函数//不过为了保险起见还是写上为好public MyView(Context context) {        super(context);        // 此处进行了必要的初始化        bitmap=BitmapFactory.decodeResource(getResources(), R.drawable.map3);        bmWidth=bitmap.getWidth();        bmHeight=bitmap.getHeight();     }//2个参数的构造函数//如果该View直接添加到了xml布局文件里,会调用此函数public MyView(Context context, AttributeSet attrs) {        super(context, attrs);        // 此处进行了必要的初始化        bitmap=BitmapFactory.decodeResource(getResources(), R.drawable.map3);        bmWidth=bitmap.getWidth();        bmHeight=bitmap.getHeight();    }//3个参数的构造函数//这个没有必要加,因为如果你去看2参调用的super(context, attrs);//它调用的就是这个3参的构造函数public MyView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        // 所以我就没在这加初始化    }

运行一下,诶,还是报错?
因为我一开始图省事儿,直接把该View类定义在需要显示它的Activity类的内部,作为一个内部类。而我估计在读取xml布局文件时,内部类还没有被编译,所以提示“找不到控件类”。
所以还需要注意:自定义控件需要单独写在一个类里。

然后万事大吉~现在可以任意的拖动大图片查看它的各个部分啦!
要说这个可以应用在何处?我猜用JAVA和Eclipse开发的安卓游戏里,如果需要查看大地图的话,应该就可以用这个方法解决吧~

不过我想一定也有其他的好的解决方法,如果你知道的话,请一定告诉我><我也想多学点儿呢

0 0