热门标签 | HotTags
当前位置:  开发笔记 > Android > 正文

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

这篇文章主要为大家详细介绍了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 &#63; 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  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  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.在布局文件中添加布局。

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

五、最终效果

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


推荐阅读
  • Netflix利用Druid实现高效实时数据分析
    本文探讨了全球领先的在线娱乐公司Netflix如何通过采用Apache Druid,实现了高效的数据采集、处理和实时分析,从而显著提升了用户体验和业务决策的准确性。文章详细介绍了Netflix在系统架构、数据摄取、管理和查询方面的实践,并展示了Druid在大规模数据处理中的卓越性能。 ... [详细]
  • 深入理解Lucene搜索机制
    本文旨在帮助读者全面掌握Lucene搜索的编写步骤、核心API及其应用。通过详细解析Lucene的基本查询和查询解析器的使用方法,结合架构图和代码示例,带领读者深入了解Lucene搜索的工作流程。 ... [详细]
  • C#设计模式学习笔记:观察者模式解析
    本文将探讨观察者模式的基本概念、应用场景及其在C#中的实现方法。通过借鉴《Head First Design Patterns》和维基百科等资源,详细介绍该模式的工作原理,并提供具体代码示例。 ... [详细]
  • 本文详细介绍了划分树这一数据结构,重点探讨了其在子树和中值计算中的应用及优化方法。 ... [详细]
  • Android Studio 安装与配置指南
    本教程详细介绍了如何下载并安装 Android Studio,包括设置 SDK 路径和优化启动性能的方法。通过这些步骤,您可以顺利地开始开发 Android 应用。 ... [详细]
  • Appium + Java 自动化测试中处理页面空白区域点击问题
    在进行移动应用自动化测试时,有时会遇到某些页面没有返回按钮,只能通过点击空白区域返回的情况。本文将探讨如何在Appium + Java环境中有效解决此类问题,并提供详细的解决方案。 ... [详细]
  • 如何清除Chrome浏览器地址栏的特定历史记录
    在使用Chrome浏览器时,你可能会发现地址栏保存了大量浏览记录。有时你可能希望删除某些特定的历史记录而不影响其他数据。本文将详细介绍如何单独删除地址栏中的特定记录以及批量清除所有历史记录的方法。 ... [详细]
  • 利用Selenium与ChromeDriver实现豆瓣网页全屏截图
    本文介绍了一种使用Selenium和ChromeDriver结合Python代码,轻松实现对豆瓣网站进行完整页面截图的方法。该方法不仅简单易行,而且解决了新版Selenium不再支持PhantomJS的问题。 ... [详细]
  • 嵌入式开发环境搭建与文件传输指南
    本文详细介绍了如何为嵌入式应用开发搭建必要的软硬件环境,并提供了通过串口和网线两种方式将文件传输到开发板的具体步骤。适合Linux开发初学者参考。 ... [详细]
  • 解决TensorFlow CPU版本安装中的依赖问题
    本文记录了在安装CPU版本的TensorFlow过程中遇到的依赖问题及解决方案,特别是numpy版本不匹配和动态链接库(DLL)错误。通过详细的步骤说明和专业建议,帮助读者顺利安装并使用TensorFlow。 ... [详细]
  • 探索新一代API文档工具,告别Swagger的繁琐
    对于后端开发者而言,编写和维护API文档既繁琐又不可或缺。本文将介绍一款全新的API文档工具,帮助团队更高效地协作,简化API文档生成流程。 ... [详细]
  • 本文详细探讨了Android Activity中View的绘制流程和动画机制,包括Activity的生命周期、View的测量、布局和绘制过程以及动画对View的影响。通过实验验证,澄清了一些常见的误解,并提供了代码示例和执行结果。 ... [详细]
  • 本文探讨了在构建应用程序时,如何对不同类型的数据进行结构化设计。主要分为三类:全局配置、用户个人设置和用户关系链。每种类型的数据都有其独特的用途和应用场景,合理规划这些数据结构有助于提升用户体验和系统的可维护性。 ... [详细]
  • 在 Android 开发中,通过 Intent 启动 Activity 或 Service 时,可以使用 putExtra 方法传递数据。接收方可以通过 getIntent().getExtras() 获取这些数据。本文将介绍如何使用 RoboGuice 框架简化这一过程,特别是 @InjectExtra 注解的使用。 ... [详细]
  • Linux中的yum安装软件
    yum俗称大黄狗作用:解决安装软件包的依赖关系当安装依赖关系的软件包时,会将依赖的软件包一起安装。本地yum:需要yum源,光驱挂载。yum源:(刚开始查看yum源中的内容就是上图 ... [详细]
author-avatar
志信俊霖2542
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有