Android中实现Bitmap在自定义View中的放大与拖动

简介: Android中实现Bitmap在自定义View中的放大与拖动

一基本实现思路:

基于View类实现自定义View –MyImageView类。在使用View的Activity类中完成OnTouchListener接口,实现对MotionEvent事件的监听与处理,常见的MotionEvent事件如下:

ACTION_DOWN事件,记录平移开始点

ACTION_UP事件,结束平移事件处理

ACTION_MOVE事件,记录平移点,计算与开始点距离,实现Bitmap平移,在多点触控时候,计算两点之间的距离,实现图像放大

ACTION_POINTER_DOWN事件,计算两点之间的距离,作为初始距离,实现图像手势放大时候使用。
ACTION_POINTER_UP事件,结束两点触控放大图像处理


放大与拖动

基于单点触控实现Bitmap对象在View上的拖动、并且检测View的边缘,防止拖动过界。基于两个点触控实现Bitmap对象在View上的放大、并且检测放大倍数。基于Matrix对象实现对Bitmap在View上放大与平移变换,Matrix对象是android中实现图像几何变换的矩阵,支持平移、放大、缩小、错切、旋转等常见操作。


Bitmap对象在View中的更新与显示

通过重载onDraw方法,使用canvas实现绘制Bitmap对象、通过view.invalidate()方法实现View的刷新。


MyImageView类的重要方法说明:

initParameters()初始化所有需要用到的参数

setStartPoint()设置图像平移的开始点坐标

setMovePoint()设置图像平移的移动点坐标,然后集合开始点位置,计算它们之间的距离,从而得到Bitmap对象需要平移的两个参数值sx、sy。其中还包括保证图像不会越过View边界的检查代码。


savePreviousResult()保存当前的平移数据,下次可以继续在次基础上平移Bitmap对象。


zoomIn()根据两个点之间的欧几里德距离,通过初始距离比较,得到放大比例,实现Bitmap在View对象上的放大




Matrix中关于放大与平移的API


Matrix.postScale方法与Matrix.postTranslate方法可以不改变Bitmap对象本身实现平移与放大。

二:代码实现


自定义View类使用xml布局如下:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >
 
    <com.example.matrixdemo.MyImageView
        android:id="@+id/myView"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:text="@string/hello_world" />
 
</RelativeLayout>

自定义View实现代码如下:

package com.example.matrixdemo;
 
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Point;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
 
public class MyImageView extends View {
  private Paint mPaint;
  private Bitmap bitmap;
  private Matrix matrix;
  
  // 平移开始点与移动点
  private Point startPoint;
  private Point movePoint;
  private float initDistance;
 
  // 记录当前平移距离
  private int sx;
  private int sy;
  
  // 保存平移状态
  private int oldsx;
  private int oldsy;
  
  // scale rate
  private float widthRate;
  private float heightRate;
  
  public MyImageView(Context context) {
    super(context);
  }
  
  public MyImageView(Context context, AttributeSet attrs) {
    super(context, attrs);
  }
  
  public void setBitmap(Bitmap bitmap) {
    this.bitmap = bitmap;
  }
  
  private void initParameters() {
    // 初始化画笔
    mPaint = new Paint();
    mPaint.setColor(Color.BLACK);
    matrix = new Matrix();
    if(bitmap != null)
    {
      float iw = bitmap.getWidth();
      float ih = bitmap.getHeight();
      float width = this.getWidth();
      float height = this.getHeight();
      // 初始放缩比率
      widthRate = width / iw;
      heightRate = height / ih;
    }
    
    sx = 0;
    sy = 0;
    
    oldsx = 0;
    oldsy = 0;
    
  }
  
  public void setStartPoint(Point startPoint) {
    this.startPoint = startPoint;
  }
  
  public void setInitDistance(float initDistance) {
    this.initDistance = initDistance;
  }
  
  public void zoomIn(float distance)
  {
    float rate = distance / this.initDistance;
    float iw = bitmap.getWidth();
    float ih = bitmap.getHeight();
    float width = this.getWidth();
    float height = this.getHeight();
    // get scale rate
    widthRate = (width / iw ) * rate;
    heightRate = (height / ih) * rate;
    
    // make it same as view size
    float iwr = (width / iw );
    float ihr = (height / ih);
    if(iwr >= widthRate)
    {
      widthRate = (width / iw );
    }
    if(ihr >= heightRate)
    {
      heightRate = (height / ih);
    }
    
    // go to center
    oldsx = (int)((width - widthRate * iw) / 2);
    oldsy = (int)((height - heightRate * ih) / 2);
  }
 
  public void setMovePoint(Point movePoint) {
    this.movePoint = movePoint;
    sx = this.movePoint.x - this.startPoint.x;
    sy = this.movePoint.y - this.startPoint.y;
    
    float iw = bitmap.getWidth();
    float ih = bitmap.getHeight();
    
    // 检测边缘
    int deltax = (int)((widthRate * iw) - this.getWidth());
    int deltay = (int)((heightRate * ih) - this.getHeight());
    if((sx + this.oldsx) >= 0)
    {
      this.oldsx = 0;
      sx = 0;
    }
    else if((sx + this.oldsx) <= -deltax)
    {
      this.oldsx = -deltax;
      sx = 0;
    }
    
    if((sy + this.oldsy) >= 0)
    {
      this.oldsy = 0;
      this.sy = 0;
    }
    else if((sy + this.oldsy) <= -deltay)
    {
      this.oldsy = -deltay;
      this.sy = 0;
    }
    
    float width = this.getWidth();
    
    // 初始放缩比率
    float iwr = width / iw;
    if(iwr == widthRate)
    {
      sx = 0;
      sy = 0;
      oldsx = 0;
      oldsy = 0;
    }
  }
  
  public void savePreviousResult()
  {
    this.oldsx = this.sx + this.oldsx;
    this.oldsy = this.sy + this.oldsy;
    
    // zero
    sx = 0;
    sy = 0;
  }
 
  @Override
  protected void onDraw(Canvas canvas) {
    if(matrix == null)
    {
      initParameters();
    }
    if(bitmap != null)
    {
      matrix.reset();
      matrix.postScale(widthRate, heightRate);
      matrix.postTranslate(oldsx+sx, oldsy + sy);
      canvas.drawBitmap(bitmap, matrix, mPaint);
    }
    else
    {
      // fill rect
      Rect rect = new Rect(0, 0, getWidth(), getHeight());
      mPaint.setAntiAlias(true);
      mPaint.setColor(Color.BLACK);
      mPaint.setStyle(Style.FILL_AND_STROKE);
      canvas.drawRect(rect, mPaint);
    }
  }
}

Activity类中实现对View的OnTouchListener监听与MotionEvent事件处理的代码如下:

package com.example.matrixdemo;
 
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Point;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
 
public class MainActivity extends Activity implements OnTouchListener {
 
  public static final int SCALE_MODE = 4;
  public static final int TRANSLATION_MODE = 2;
  public static final int NULL_MODE = 1;
  private MyImageView myView;
  private int mode;
 
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    startMyImageView();
  }
 
  private void startMyImageView() {
    myView = (MyImageView) this.findViewById(R.id.myView);
    Bitmap bitmap = BitmapFactory.decodeResource(this.getResources(),
        R.drawable.flower_001);
    myView.setBitmap(bitmap);
    myView.setOnTouchListener(this);
    myView.invalidate();
  }
 
  @Override
  public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.main, menu);
    return true;
  }
 
  @Override
  public boolean onTouch(View view, MotionEvent event) {
    Log.i("touch event","touch x = " + event.getX());
    switch (MotionEvent.ACTION_MASK & event.getAction()) 
    {
      case MotionEvent.ACTION_DOWN:
        mode = TRANSLATION_MODE;
        myView.setStartPoint(new Point((int)event.getX(), (int)event.getY()));
        break;
      case MotionEvent.ACTION_POINTER_UP:
      case MotionEvent.ACTION_OUTSIDE:
      case MotionEvent.ACTION_UP:
        mode = NULL_MODE;
        myView.savePreviousResult();
        break;
      case MotionEvent.ACTION_POINTER_DOWN:
        mode = SCALE_MODE;
        myView.setInitDistance(calculateDistance(event));
        break;
      case MotionEvent.ACTION_MOVE:
        if(mode == SCALE_MODE)
        {
          float dis = calculateDistance(event);
          myView.zoomIn(dis);
        }
        else if(mode == TRANSLATION_MODE)
        {
          myView.setMovePoint(new Point((int)event.getX(), (int)event.getY()));
        }
        else
        {
          Log.i("unknow mode tag","do nothing......");
        }
        break;
    }
    myView.invalidate();
    return true;
  }
 
  private float calculateDistance(MotionEvent event) {
    float dx = event.getX(0) - event.getX(1);
    float dy = event.getY(0)  - event.getY(1);
    float distance = (float)Math.sqrt(dx*dx + dy*dy);
    return distance;
  }
 
}


三:运行效果如下

相关文章
|
6月前
|
Android开发 UED 计算机视觉
Android自定义view之线条等待动画(灵感来源:金铲铲之战)
本文介绍了一款受游戏“金铲铲之战”启发的Android自定义View——线条等待动画的实现过程。通过将布局分为10份,利用`onSizeChanged`测量最小长度,并借助画笔绘制动态线条,实现渐变伸缩效果。动画逻辑通过四个变量控制线条的增长与回退,最终形成流畅的等待动画。代码中详细展示了画笔初始化、线条绘制及动画更新的核心步骤,并提供完整源码供参考。此动画适用于加载场景,提升用户体验。
510 5
Android自定义view之线条等待动画(灵感来源:金铲铲之战)
|
6月前
|
XML Java Android开发
Android自定义view之网易云推荐歌单界面
本文详细介绍了如何通过自定义View实现网易云音乐推荐歌单界面的效果。首先,作者自定义了一个圆角图片控件`MellowImageView`,用于绘制圆角矩形图片。接着,通过将布局放入`HorizontalScrollView`中,实现了左右滑动功能,并使用`ViewFlipper`添加图片切换动画效果。文章提供了完整的代码示例,包括XML布局、动画文件和Java代码,最终展示了实现效果。此教程适合想了解自定义View和动画效果的开发者。
314 65
Android自定义view之网易云推荐歌单界面
|
6月前
|
XML 前端开发 Android开发
一篇文章带你走近Android自定义view
这是一篇关于Android自定义View的全面教程,涵盖从基础到进阶的知识点。文章首先讲解了自定义View的必要性及简单实现(如通过三个构造函数解决焦点问题),接着深入探讨Canvas绘图、自定义属性设置、动画实现等内容。还提供了具体案例,如跑马灯、折线图、太极图等。此外,文章详细解析了View绘制流程(measure、layout、draw)和事件分发机制。最后延伸至SurfaceView、GLSurfaceView、SVG动画等高级主题,并附带GitHub案例供实践。适合希望深入理解Android自定义View的开发者学习参考。
653 84
|
6月前
|
前端开发 Android开发 UED
讲讲Android为自定义view提供的SurfaceView
本文详细介绍了Android中自定义View时使用SurfaceView的必要性和实现方式。首先分析了在复杂绘制逻辑和高频界面更新场景下,传统View可能引发卡顿的问题,进而引出SurfaceView作为解决方案。文章通过Android官方Demo展示了SurfaceView的基本用法,包括实现`SurfaceHolder.Callback2`接口、与Activity生命周期绑定、子线程中使用`lockCanvas()`和`unlockCanvasAndPost()`方法完成绘图操作。
181 3
|
测试技术 Android开发 Web App开发
|
29天前
|
移动开发 JavaScript 应用服务中间件
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
188 5
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
|
1月前
|
移动开发 前端开发 Android开发
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
230 12
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
|
1月前
|
移动开发 Rust JavaScript
【01】首页建立-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【01】首页建立-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
490 3
【01】首页建立-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
|
2月前
|
开发工具 Android开发
X Android SDK file not found: adb.安卓开发常见问题-Android SDK 缺少 `adb`(Android Debug Bridge)-优雅草卓伊凡
X Android SDK file not found: adb.安卓开发常见问题-Android SDK 缺少 `adb`(Android Debug Bridge)-优雅草卓伊凡
467 11
X Android SDK file not found: adb.安卓开发常见问题-Android SDK 缺少 `adb`(Android Debug Bridge)-优雅草卓伊凡
|
1月前
|
移动开发 Android开发
【03】建立隐私关于等相关页面和内容-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【03】建立隐私关于等相关页面和内容-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
113 0