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


三:运行效果如下

相关文章
|
1天前
|
搜索推荐 数据库 Android开发
自定义头像 Android
【6月更文挑战第16天】
|
1天前
|
开发工具 Android开发
Android 代码自定义drawble文件实现View圆角背景
Android 代码自定义drawble文件实现View圆角背景
10 0
|
1天前
|
Android开发
Android 自定义View 测量控件宽高、自定义viewgroup测量
Android 自定义View 测量控件宽高、自定义viewgroup测量
8 0
|
1天前
|
开发工具 Android开发 git
Android自定义View——可以设置最大宽高的FrameLayout
Android自定义View——可以设置最大宽高的FrameLayout
12 0
|
1天前
|
JSON Android开发 数据格式
Android动态添加view设置view大小(宽高)
Android动态添加view设置view大小(宽高)
5 0
|
算法 Android开发
Android自定义控件 | View绘制原理(画多大?)
这一篇将以源码中的几个关键函数为线索分析“测量(measure)”。 如果想直接看结论可以移步到第三篇末尾。 真正的测量工作在onMeasure()中进行。。。
95 0
|
Linux Android开发 开发者
Android窗口管理分析(1):View如何绘制到屏幕上的主观理解
Android窗口管理分析(1):View如何绘制到屏幕上的主观理解
234 0
Android窗口管理分析(1):View如何绘制到屏幕上的主观理解
|
XML Android开发 数据格式
|
Android开发
Android View底层到底是怎么绘制的
Android绘制链图: 网上很多讲Android  view的绘制流程往往只讲到了Measure - Layout - Draw。 但是,这只是一个大体的流程,而我们需要探讨的是Android在我们调用setcontentView()之后,系统给我们干了什么事情,这个完整的逻辑是什么样的,却很少有人讲,还是先看下系统代码吧。 public void s
1271 0