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

五、最终效果

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

(0)

相关推荐

  • Android之在linux终端执行shell脚本直接打印当前运行app的日志的实现方法

    1.问题 我们一般很多时候会需要在ubuntu终端上打印当前运行app的日志,我们一般常见的做法是 1).获取包名 打开当前运行的app,然后输入如下命令,然后在第一行TASK后面的就可以看到包名 adb shell dumpsys activity top 2).我们的终端安装了pidcat.py脚本,然后执行如下的命令就可以打印当前运行app的全日志, pidcat.py packageName 3).思考,为什么每次都需要这样重复的操作呢?一说到重复,我们应该立马想到是否可以用脚本解决重复

  • Android线程中Handle的使用讲解

    Android UI线程是不安全的,子线程中进行UI操作,可能会导致程序的崩溃,解决办法:创建一个Message对象,然后借助Handler发送出去,之后在Handler的handleMessage()方法中获得刚才发送的Message对象,然后在这里进行UI操作就不会再出现崩溃了 定义类继承Handler public class BallHandler extends Handler{ ImageView imageview; Bitmap bitmap; public BallHandle

  • Android可自定义神奇动效的卡片切换视图实例

    前言 面对众多卡片层叠效果,我们的产品童鞋也突发奇想,搞出了另一种卡片层叠切换展示的交互,而且产品狗们居然要求多做几种动效给他们看,好让他们选择,这简直就是要搞事情啊,what are you 弄啥咧?! "哥哥我做不到啊.....啊.....呸",做为一名有节操的程序猿,自然是不能说出这么没有出息的话,哥就满足你们,于是,出了个可自定义动效的卡片切换视图,效果如下所示 思路 首先,要展示出卡片层叠的视觉效果.在这里,我们通过方块的缩放大小差异以及在Y方向上的位置差异,来展现这种视觉效

  • Android手机获取Mac地址的几种方法

    最常用的方法,通过WiFiManager获取: /** * 通过WiFiManager获取mac地址 * @param context * @return */ private static String tryGetWifiMac(Context context) { WifiManager wm = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE); WifiInfo wi

  • Android获取当前应用分配的最大内存和目前使用内存的方法

    在Android里,程序内存被分为2部分:native和dalvik,dalvik就是我们普通的Java使用内存,分析堆栈的时候使用的内存.我们创建的对象是在这里面分配的,对于内存的限制是 native+dalvik 不能超过最大限制. Android 原生系统一般默认16M,但是国内手机一般都是特殊定制的,都有修改系统的内存大小,所有有时候,要查看具体应用系统分配的内存大小,还是需要实际去测试的, 测试方法如下: 方式一: ActivityManager activityManager = (

  • Android亮屏速度分析总结

    前面聊的 最近在调试项目的亮屏速度,我们希望在按下power键后到亮屏这个时间能达到500MS以内,在Rockchip 3399和3288上面的时间都不能达到要求,因此引发了一系列的调试之路. 计算按下power键到亮屏的时间 Android 唤醒时间统计 刚开始的时候,我只在android阶段统计时间,也能看到时间的差异,但是不是最准确的,我统计的时间日志如下 01-18 09:13:40.992 683 772 D SurfaceControl: Excessive delay in set

  • Android传感器SensorEventListener之加速度传感器

    这个类(我的是Activity中)继承SensorEventListener接口 先获取传感器对象,再获取传感器对象的类型 //获取传感器管理对象 SensorManager mSensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE); // 获取传感器的类型(TYPE_ACCELEROMETER:加速度传感器) Sensor mSensor = mSensorManager.getDefaultSensor(

  • Android添加音频的几种方法

    在res文件夹中新建一个文件夹,命名为raw.在里面放入我们需要的音频文件. 第一种: // 根据资源创建播放器对象 player = MediaPlayer.create(this, R.raw.xiaoxiaole); try { player.prepare();// 同步 } catch (IllegalStateException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOExcept

  • Android Java调用自己C++类库的实例讲解

    Android Java 如何调用自己的 C++ 的类库 下面以 Java 调用 C++ 的加法运算函数为例,做简单说明. (使用 Android Studio 3 编译) 首先编译 c++ 类库 创建独立目录存放 c++ 文件,例如 "app/src/main/cpp/add.cpp",内容如下 #include <jni.h> extern "C" JNIEXPORT jint JNICALL Java_com_example_liyi_demo_U

  • Android调试神器stetho使用详解和改造

    概述 stetho是Facebook开源的一个Android调试工具,项目地址:facebook/stetho 通过Stetho,开发者可以使用chrome的inspect功能,对Android应用进行调试和查看. 功能概述 stetho提供的功能主要有: Network Inspection:网络抓包,如果你使用的是当前流行的OkHttp或者Android自带的 HttpURLConnection,你可以轻松地在chrome inspect窗口的network一栏抓到所有的网络请求和回包,还用

随机推荐