Android实现图片在屏幕内缩放和移动效果
通常我们遇到的图片缩放需求,都是图片基于屏幕自适应后,进行缩放和移动,且图片最小只能是自适应的大小。最近遇到一个需求,要求图片只能在屏幕内缩放和移动,不能超出屏幕。
一、需求
在屏幕中加载一张图片,图片可以手势缩放移动。但是图片最大只能缩放到屏幕大小,也只允许在屏幕内移动。可以从系统中读取图片(通过绝对路径),也可以从资源文件中读取图片。
二、自定义zoomimageview
屏幕内手势缩放图片与普通的图片缩放相比,比较麻烦的是,需要计算图片的精确位置。不同于普通缩放的图片充满屏幕,屏内缩放的图片只占据屏幕的一部分,我们需要判断手指是否点在图片内,才能进行各种操作。
/** * 判断手指是否点在图片内(单指) */ private void isclickinimage(){ if (translationx <= mfirstx && mfirstx <= (translationx + currentw) && translationy <= mfirsty && mfirsty <= (translationy + currenth)){ isclickinimage = true; }else { isclickinimage = false; } } /** * 判断手指是否点在图片内(双指) * 只要有一只手指在图片内就为true * @param event */ private void isclickinimage(motionevent event){ if (translationx <= event.getx(0) && event.getx(0) <= (translationx + currentw) && translationy <= event.gety(0) && event.gety(0) <= (translationy + currenth)){ isclickinimage = true; }else if (translationx <= event.getx(1) && event.getx(1) <= (translationx + currentw) && translationy <= event.gety(1) && event.gety(1) <= (translationy + currenth)){ isclickinimage = true; }else { isclickinimage = false; } }
其他的各种操作,之于缩放,移动,边界检查等,和普通的图片缩放没有太多区别。完整代码如下:
package com.uni.myapplication; import android.content.context; import android.graphics.bitmap; import android.graphics.bitmapfactory; import android.graphics.canvas; import android.graphics.matrix; import android.graphics.paint; import android.support.annotation.nullable; import android.util.attributeset; import android.view.motionevent; import android.view.view; import java.io.file; /** * created by newcboy on 2018/3/9. */ public class zoomimageview extends view { public static final int image_max_size = 1000;//加载图片允许的最大size,单位kb private float minimal = 100.0f; private float screenw;//屏幕宽度 private float screenh;//屏幕高度 //单指按下的坐标 private float mfirstx = 0.0f; private float mfirsty = 0.0f; //单指离开的坐标 private float lastmovex =-1f; private float lastmovey =-1f; //两指的中点坐标 private float centpointx; private float centpointy; //图片的绘制坐标 private float translationx = 0.0f; private float translationy = 0.0f; //图片的原始宽高 private float primaryw; private float primaryh; //图片当前宽高 private float currentw; private float currenth; private float scale = 1.0f; private float maxscale, minscale; private bitmap bitmap; private matrix matrix; private int mlocker = 0; private float fingerdistance = 0.0f; private boolean isloaded = false; private boolean isclickinimage = false; public zoomimageview(context context, @nullable attributeset attrs) { super(context, attrs); } /** * 从资源文件中读取图片 * @param context * @param imageid */ public void setresourcebitmap(context context, int imageid){ bitmap = bitmapfactory.decoderesource(context.getresources(), imageid); isloaded = true; primaryw = bitmap.getwidth(); primaryh = bitmap.getheight(); matrix = new matrix(); } /** * 根据路径添加图片 * @param path * @param scale */ public void setimagepathbitmap(string path, float scale){ this.scale = scale; setimagebitmap(path); } private void setimagebitmap(string path){ file file = new file(path); if (file.exists()){ isloaded = true; bitmap = imageloadutils.getimageloadbitmap(path, image_max_size); primaryw = bitmap.getwidth(); primaryh = bitmap.getheight(); matrix = new matrix(); }else { isloaded = false; } } @override protected void onlayout(boolean changed, int left, int top, int right, int bottom) { super.onlayout(changed, left, top, right, bottom); if (changed){ screenw = getwidth(); screenh = getheight(); translationx = (screenw - bitmap.getwidth() * scale)/ 2; translationy = (screenh - bitmap.getheight() * scale) / 2; setmaxminscale(); } } /** * */ private void setmaxminscale(){ float xscale, yscale; xscale = minimal / primaryw; yscale = minimal / primaryh; minscale = xscale > yscale ? xscale : yscale; xscale = primaryw / screenw; yscale = primaryh / screenh; if (xscale > 1 || yscale > 1 ) { if (xscale > yscale) { maxscale = 1/xscale; }else { maxscale = 1/yscale; } }else { if (xscale > yscale) { maxscale = 1/xscale; }else { maxscale = 1/yscale; } } if (isscaleerror()){ restoreaction(); } } @override public boolean ontouchevent(motionevent event) { if (!isloaded){ return true; } switch (event.getactionmasked()){ case motionevent.action_down: mfirstx = event.getx(); mfirsty = event.gety(); isclickinimage(); break; case motionevent.action_pointer_down: fingerdistance = getfingerdistance(event); isclickinimage(event); break; case motionevent.action_move: float fingernum = event.getpointercount(); if (fingernum == 1 && mlocker == 0 && isclickinimage){ movingaction(event); }else if (fingernum == 2 && isclickinimage){ zoomaction(event); } break; case motionevent.action_pointer_up: mlocker = 1; if (isscaleerror()){ translationx = (event.getx(1) + event.getx(0)) / 2; translationy = (event.gety(1) + event.gety(0)) / 2; } break; case motionevent.action_up: lastmovex = -1; lastmovey = -1; mlocker = 0; if (isscaleerror()){ restoreaction(); } break; } return true; } /** * 移动操作 * @param event */ private void movingaction(motionevent event){ float movex = event.getx(); float movey = event.gety(); if (lastmovex == -1 || lastmovey == -1) { lastmovex = movex; lastmovey = movey; } float movedistancex = movex - lastmovex; float movedistancey = movey - lastmovey; translationx = translationx + movedistancex; translationy = translationy + movedistancey; lastmovex = movex; lastmovey = movey; invalidate(); } /** * 缩放操作 * @param event */ private void zoomaction(motionevent event){ midpoint(event); float currentdistance = getfingerdistance(event); if (math.abs(currentdistance - fingerdistance) > 1f) { float movescale = currentdistance / fingerdistance; scale = scale * movescale; translationx = translationx * movescale + centpointx * (1-movescale); translationy = translationy * movescale + centpointy * (1-movescale); fingerdistance = currentdistance; invalidate(); } } /** * 图片恢复到指定大小 */ private void restoreaction(){ if (scale < minscale){ scale = minscale; }else if (scale > maxscale){ scale = maxscale; } translationx = translationx - bitmap.getwidth()*scale / 2; translationy = translationy - bitmap.getheight()*scale / 2; invalidate(); } /** * 判断手指是否点在图片内(单指) */ private void isclickinimage(){ if (translationx <= mfirstx && mfirstx <= (translationx + currentw) && translationy <= mfirsty && mfirsty <= (translationy + currenth)){ isclickinimage = true; }else { isclickinimage = false; } } /** * 判断手指是否点在图片内(双指) * 只要有一只手指在图片内就为true * @param event */ private void isclickinimage(motionevent event){ if (translationx <= event.getx(0) && event.getx(0) <= (translationx + currentw) && translationy <= event.gety(0) && event.gety(0) <= (translationy + currenth)){ isclickinimage = true; }else if (translationx <= event.getx(1) && event.getx(1) <= (translationx + currentw) && translationy <= event.gety(1) && event.gety(1) <= (translationy + currenth)){ isclickinimage = true; }else { isclickinimage = false; } } /** * 获取两指间的距离 * @param event * @return */ private float getfingerdistance(motionevent event){ float x = event.getx(1) - event.getx(0); float y = event.gety(1) - event.gety(0); return (float) math.sqrt(x * x + y * y); } /** * 判断图片大小是否符合要求 * @return */ private boolean isscaleerror(){ if (scale > maxscale || scale < minscale){ return true; } return false; } /** * 获取两指间的中点坐标 * @param event */ private void midpoint(motionevent event){ centpointx = (event.getx(1) + event.getx(0))/2; centpointy = (event.gety(1) + event.gety(0))/2; } @override protected void ondraw(canvas canvas) { super.ondraw(canvas); if (isloaded){ imagezoomview(canvas); } } private void imagezoomview(canvas canvas){ currentw = primaryw * scale; currenth = primaryh * scale; matrix.reset(); matrix.postscale(scale, scale);//x轴y轴缩放 peripheryjudge(); matrix.posttranslate(translationx, translationy);//中点坐标移动 canvas.drawbitmap(bitmap, matrix, null); } /** * 图片边界检查 * (只在屏幕内) */ private void peripheryjudge(){ if (translationx < 0){ translationx = 0; } if (translationy < 0){ translationy = 0; } if ((translationx + currentw) > screenw){ translationx = screenw - currentw; } if ((translationy + currenth) > screenh){ translationy = screenh - currenth; } } }
实际上,用bitmap绘制图片时,可以通过paint设置图片透明度。
paint paint = new paint(); paint.setstyle( paint.style.stroke); paint.setalpha(150);
在setalpha()中传入一个0~255的整数。数字越大,透明度越低。
然后在绘制图片时
canvas.drawbitmap(bitmap, matrix, paint);
三、imageloadutils图片加载类
这个类是对传入的图片进行压缩处理的类,在应用从系统中读取图片时用到。在写这个类时,发现一些和网上说法不一样的地方。
options.insamplesize这个属性,网上的说法是必须是2的幂次方,但实际上,我的验证结果是所有的整数都可以。
这里采用的压缩方法是,获取系统剩余内存和图片大小,然后将图片压缩到合适的大小。
package com.uni.myapplication; import android.app.activitymanager; import android.content.context; import android.graphics.bitmap; import android.graphics.bitmapfactory; import android.graphics.bitmapfactory.options; import android.net.uri; import java.io.file; import java.io.fileinputstream; /** * 图片加载工具类 * * created by newcboy on 2018/1/25. */ public class imageloadutils { /** * 原图加载,根据传入的指定图片大小。 * @param imagepath * @param maxsize * @return */ public static bitmap getimageloadbitmap(string imagepath, int maxsize){ int filesize = 1; bitmap bitmap = null; int simplesize = 1; file file = new file(imagepath); if (file.exists()) { uri imageuri = uri.parse(imagepath); try { filesize = (int) (getfilesize(file) / 1024); } catch (exception e) { e.printstacktrace(); } options options = new options(); if (filesize > maxsize){ for (simplesize = 2; filesize>= maxsize; simplesize++){ filesize = filesize / simplesize; } } options.insamplesize = simplesize; bitmap = bitmapfactory.decodefile(imageuri.getpath(), options); } return bitmap; } /** * 获取指定文件的大小 * @param file * @return * @throws exception */ public static long getfilesize(file file) throws exception{ if(file == null) { return 0; } long size = 0; if(file.exists()) { fileinputstream minputstream = new fileinputstream(file); size = minputstream.available(); } return size; } /** * 获取手机运行内存 * @param context * @return */ public static long gettotalmemorysize(context context){ long size = 0; activitymanager activitymanager = (activitymanager) context.getsystemservice(context.activity_service); activitymanager.memoryinfo outinfo = new activitymanager.memoryinfo();//outinfo对象里面包含了内存相关的信息 activitymanager.getmemoryinfo(outinfo);//把内存相关的信息传递到outinfo里面c++思想 //size = outinfo.totalmem; //总内存 size = outinfo.availmem; //剩余内存 return (size/1024/1024); } }
四、调用
使用方法和通常的控件差不多,只是多了一个设置图片的方法。
1.在布局文件中添加布局。
<com.uni.myapplication.zoomimageview android:id="@+id/zoom_image_view" android:layout_width="wrap_content" android:layout_height="wrap_content" />
2.在代码中调用
zoomimageview = (zoomimageview) findviewbyid(r.id.zoom_image_view); zoomimageview.setimagepathbitmap(mainactivity.this, imagepath, 1.0f); zoomimageview.setresourcebitmap(mainactivity.this, r.mipmap.ic_launcher);
其中setimagepathbitmap()是从系统中读取图片加载的方法,setresourcebitmap()是从资源文件中读取图片的方法。
当然,从系统读取图片需要添加读写权限,这个不能忘了。而且6.0以上的系统需要动态获取权限。动态获取权限的方法这里就不介绍了,网上有很详细的说明。
五、最终效果
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。