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;
  }
 
}


三:运行效果如下

相关文章
|
8天前
|
Android开发
Android面试题之自定义View注意事项
在Android开发中,自定义View主要分为四类:直接继承View重写onDraw,继承ViewGroup创建布局,扩展特定View如TextView,以及继承特定ViewGroup。实现时需注意:支持wrap_content通过onMeasure处理,支持padding需在onDraw或onMeasure/onLayout中处理。避免在View中使用Handler,使用post系列方法代替。记得在onDetachedFromWindow时停止线程和动画以防止内存泄漏。处理滑动嵌套时解决滑动冲突,并避免在onDraw中大量创建临时对象。
15 4
|
6天前
|
Android开发
Android面试题之View的invalidate方法和postInvalidate方法有什么区别
本文探讨了Android自定义View中`invalidate()`和`postInvalidate()`的区别。`invalidate()`在UI线程中刷新View,而`postInvalidate()`用于非UI线程,通过消息机制切换到UI线程执行`invalidate()`。源码分析显示,`postInvalidate()`最终调用`ViewRootImpl`的`dispatchInvalidateDelayed`,通过Handler发送消息到UI线程执行刷新。
14 1
|
11天前
|
前端开发 API Android开发
Android自定义View之Canvas一文搞定
这篇文章介绍了Android自定义View中如何使用Canvas和Paint来绘制图形。Canvas可理解为画布,用于绘制各种形状如文字、点、线、矩形、圆角矩形、圆和弧。常见API包括`drawText()`、`drawPoint()`、`drawLine()`、`drawRect()`等。文章还提到了Canvas的保存、恢复、平移和旋转方法,通过绘制钟表盘的例子展示了如何实际应用。总结关键点:Canvas与Paint结合用于图像绘制,掌握Canvas的基本绘图函数及坐标变换操作是自定义View的关键。
10 0
Android自定义View之Canvas一文搞定
|
11天前
|
消息中间件 前端开发 Android开发
Android面试题自定义View之Window、ViewRootImpl和View的三大流程
Android开发中,View的三大核心流程包括measure(测量)、layout(布局)和draw(绘制)。MeasureSpec类在测量过程中起到关键作用,它结合尺寸大小和模式(EXACTLY、AT_MOST、UNSPECIFIED)来指定View应如何测量。onMeasure方法用于自定义View的测量,布局阶段,ViewGroup调用onLayout确定子元素位置,而draw阶段按照特定顺序绘制背景、内容、子元素和装饰。整个流程始于ViewRootImpl的performTraversals,该方法触发测量、布局和绘制。
16 0
|
17天前
|
XML 数据格式
Android-自定义三角形评分控件
Android-自定义三角形评分控件
13 0
|
算法 Android开发
Android自定义控件 | View绘制原理(画多大?)
这一篇将以源码中的几个关键函数为线索分析“测量(measure)”。 如果想直接看结论可以移步到第三篇末尾。 真正的测量工作在onMeasure()中进行。。。
98 0
|
Linux Android开发 开发者
Android窗口管理分析(1):View如何绘制到屏幕上的主观理解
Android窗口管理分析(1):View如何绘制到屏幕上的主观理解
238 0
Android窗口管理分析(1):View如何绘制到屏幕上的主观理解
|
XML Android开发 数据格式
|
Android开发
Android View底层到底是怎么绘制的
Android绘制链图: 网上很多讲Android  view的绘制流程往往只讲到了Measure - Layout - Draw。 但是,这只是一个大体的流程,而我们需要探讨的是Android在我们调用setcontentView()之后,系统给我们干了什么事情,这个完整的逻辑是什么样的,却很少有人讲,还是先看下系统代码吧。 public void s
1273 0