Android实现图片在屏幕内缩放和移动效果

作者:newcboy 时间:2021-10-28 12:24:57 

通常我们遇到的图片缩放需求,都是图片基于屏幕自适应后,进行缩放和移动,且图片最小只能是自适应的大小。最近遇到一个需求,要求图片只能在屏幕内缩放和移动,不能超出屏幕。

一、需求

在屏幕中加载一张图片,图片可以手势缩放移动。但是图片最大只能缩放到屏幕大小,也只允许在屏幕内移动。可以从系统中读取图片(通过绝对路径),也可以从资源文件中读取图片。

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以上的系统需要动态获取权限。动态获取权限的方法这里就不介绍了,网上有很详细的说明。

五、最终效果

Android实现图片在屏幕内缩放和移动效果

来源:https://blog.csdn.net/newcboy/article/details/79523316

标签:Android,图片缩放,图片移动
0
投稿

猜你喜欢

  • Java中逆序遍历List集合的实现

    2022-04-03 23:48:13
  • Java Swing组件文件选择器JFileChooser简单用法示例

    2021-09-23 21:00:34
  • spring boot补习系列之几种scope详解

    2022-06-10 13:39:13
  • Mybatis分页插件PageHelper的配置和简单使用方法(推荐)

    2022-03-20 06:34:55
  • Java将Exception信息转为String字符串的方法

    2022-12-01 08:39:35
  • java实现双色球机选号码生成器

    2022-10-05 07:41:33
  • MyBatis实现物理分页的实例

    2023-03-13 04:21:45
  • java生成随机数的方法

    2023-12-12 12:49:51
  • JavaWeb开发之使用jQuery与Ajax实现动态联级菜单效果

    2023-11-28 19:46:08
  • Java使用BigDecimal进行运算封装的实际案例

    2023-06-20 02:22:26
  • Spring Security如何实现升级密码加密方式详解

    2023-09-02 08:47:31
  • intellij idea14打包apk文件和查看sha1值

    2022-05-25 13:18:37
  • Java过滤器模式原理及用法实例

    2023-03-07 12:15:07
  • 详解Java数据库连接JDBC基础知识(操作数据库:增删改查)

    2023-08-22 23:47:37
  • SpringMVC之@requestBody的作用及说明

    2022-06-08 12:35:04
  • C#图片查看器实现方法

    2021-06-17 16:50:03
  • SpringBoot小程序推送信息的项目实践

    2021-12-07 04:23:34
  • C#用户定义类型转换详解

    2022-06-07 11:44:32
  • SuperSocket入门--Telnet服务器和客户端请求处理

    2021-07-24 19:35:14
  • SpringBoot如何实现Tomcat自动配置

    2022-04-28 02:44:10
  • asp之家 软件编程 m.aspxhome.com