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


三:运行效果如下

相关文章
|
12天前
|
搜索推荐 前端开发 Android开发
安卓应用开发中的自定义视图实现
【10月更文挑战第30天】在安卓开发的海洋中,自定义视图是那抹不可或缺的亮色,它为应用界面的个性化和交互体验的提升提供了无限可能。本文将深入探讨如何在安卓平台创建自定义视图,并展示如何通过代码实现这一过程。我们将从基础出发,逐步引导你理解自定义视图的核心概念,然后通过一个实际的代码示例,详细讲解如何将理论应用于实践,最终实现一个美观且具有良好用户体验的自定义控件。无论你是想提高自己的开发技能,还是仅仅出于对安卓开发的兴趣,这篇文章都将为你提供价值。
|
14天前
|
Android开发 开发者 UED
安卓开发中自定义View的实现与性能优化
【10月更文挑战第28天】在安卓开发领域,自定义View是提升应用界面独特性和用户体验的重要手段。本文将深入探讨如何高效地创建和管理自定义View,以及如何通过代码和性能调优来确保流畅的交互体验。我们将一起学习自定义View的生命周期、绘图基础和事件处理,进而探索内存和布局优化技巧,最终实现既美观又高效的安卓界面。
27 5
|
21天前
|
缓存 数据处理 Android开发
在 Android 中使用 RxJava 更新 View
【10月更文挑战第20天】使用 RxJava 来更新 View 可以提供更优雅、更高效的解决方案。通过合理地运用操作符和订阅机制,我们能够轻松地处理异步数据并在主线程中进行 View 的更新。在实际应用中,需要根据具体情况进行灵活运用,并注意相关的注意事项和性能优化,以确保应用的稳定性和流畅性。可以通过不断的实践和探索,进一步掌握在 Android 中使用 RxJava 更新 View 的技巧和方法,为开发高质量的 Android 应用提供有力支持。
|
21天前
|
缓存 调度 Android开发
Android 在子线程更新 View
【10月更文挑战第21天】在 Android 开发中,虽然不能直接在子线程更新 View,但通过使用 Handler、AsyncTask 或 RxJava 等方法,可以实现子线程操作并在主线程更新 View 的目的。在实际应用中,需要根据具体情况选择合适的方法,并注意相关的注意事项和性能优化,以确保应用的稳定性和流畅性。可以通过不断的实践和探索,进一步掌握在子线程更新 View 的技巧和方法,为开发高质量的 Android 应用提供支持。
30 2
|
22天前
|
XML 前端开发 Android开发
Android面试高频知识点(3) 详解Android View的绘制流程
Android面试高频知识点(3) 详解Android View的绘制流程
Android面试高频知识点(3) 详解Android View的绘制流程
|
26天前
|
XML 前端开发 Android开发
Android面试高频知识点(3) 详解Android View的绘制流程
Android面试高频知识点(3) 详解Android View的绘制流程
23 2
|
3天前
|
搜索推荐 Android开发 开发者
探索安卓开发中的自定义视图:打造个性化UI组件
【10月更文挑战第39天】在安卓开发的世界中,自定义视图是实现独特界面设计的关键。本文将引导你理解自定义视图的概念、创建流程,以及如何通过它们增强应用的用户体验。我们将从基础出发,逐步深入,最终让你能够自信地设计和实现专属的UI组件。
|
5天前
|
Android开发 Swift iOS开发
探索安卓与iOS开发的差异和挑战
【10月更文挑战第37天】在移动应用开发的广阔舞台上,安卓和iOS这两大操作系统扮演着主角。它们各自拥有独特的特性、优势以及面临的开发挑战。本文将深入探讨这两个平台在开发过程中的主要差异,从编程语言到用户界面设计,再到市场分布的不同影响,旨在为开发者提供一个全面的视角,帮助他们更好地理解并应对在不同平台上进行应用开发时可能遇到的难题和机遇。
|
7天前
|
XML 存储 Java
探索安卓开发之旅:从新手到专家
【10月更文挑战第35天】在数字化时代,安卓应用的开发成为了一个热门话题。本文旨在通过浅显易懂的语言,带领初学者了解安卓开发的基础知识,同时为有一定经验的开发者提供进阶技巧。我们将一起探讨如何从零开始构建第一个安卓应用,并逐步深入到性能优化和高级功能的实现。无论你是编程新手还是希望提升技能的开发者,这篇文章都将为你提供有价值的指导和灵感。