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

简介: 一基本实现思路:基于View类实现自定义View –MyImageView类。在使用View的Activity类中完成OnTouchListener接口,实现对MotionEvent事件的监听与处理,常见的MotionEvent事件如下:ACTION_DOWN事件,记录平移开始点ACTION_UP事件,结束平移事件处理ACTION_MOVE事件,记录平移点,计算与开始点距离,实现Bitmap平移,在多点触控时候,计算两点之间的距离,实现图像放大ACTION_POINTER_DOWN事件,计算两点之间的距离,作为初始距离,实现图像手势放大时候使用。
一基本实现思路:

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

}
三:运行效果如下


目录
相关文章
|
22天前
|
供应链 物联网 区块链
未来触手可及:探索新兴技术的趋势与应用安卓开发中的自定义视图:从基础到进阶
【8月更文挑战第30天】随着科技的飞速发展,新兴技术如区块链、物联网和虚拟现实正在重塑我们的世界。本文将深入探讨这些技术的发展趋势和应用场景,带你领略未来的可能性。
|
6天前
|
存储 缓存 编解码
Android经典面试题之图片Bitmap怎么做优化
本文介绍了图片相关的内存优化方法,包括分辨率适配、图片压缩与缓存。文中详细讲解了如何根据不同分辨率放置图片资源,避免图片拉伸变形;并通过示例代码展示了使用`BitmapFactory.Options`进行图片压缩的具体步骤。此外,还介绍了Glide等第三方库如何利用LRU算法实现高效图片缓存。
37 20
Android经典面试题之图片Bitmap怎么做优化
|
1天前
|
Android开发 开发者
安卓开发中的自定义视图:从入门到精通
【9月更文挑战第19天】在安卓开发的广阔天地中,自定义视图是一块充满魔力的土地。它不仅仅是代码的堆砌,更是艺术与科技的完美结合。通过掌握自定义视图,开发者能够打破常规,创造出独一无二的用户界面。本文将带你走进自定义视图的世界,从基础概念到实战应用,一步步展示如何用代码绘出心中的蓝图。无论你是初学者还是有经验的开发者,这篇文章都将为你打开一扇通往创意和效率的大门。让我们一起探索自定义视图的秘密,将你的应用打造成一件艺术品吧!
18 10
|
6天前
|
XML 编解码 Android开发
安卓开发中的自定义视图控件
【9月更文挑战第14天】在安卓开发中,自定义视图控件是一种高级技巧,它可以让开发者根据项目需求创建出独特的用户界面元素。本文将通过一个简单示例,引导你了解如何在安卓项目中实现自定义视图控件,包括创建自定义控件类、处理绘制逻辑以及响应用户交互。无论你是初学者还是有经验的开发者,这篇文章都会为你提供有价值的见解和技巧。
14 3
|
7天前
|
前端开发 Android开发 开发者
安卓应用开发中的自定义视图基础
【9月更文挑战第13天】在安卓开发的广阔天地中,自定义视图是一块神奇的画布,它允许开发者将想象力转化为用户界面的创新元素。本文将带你一探究竟,了解如何从零开始构建自定义视图,包括绘图基础、触摸事件处理,以及性能优化的实用技巧。无论你是想提升应用的视觉吸引力,还是追求更流畅的交互体验,这里都有你需要的金钥匙。
|
10天前
|
缓存 搜索推荐 Android开发
安卓应用开发中的自定义View组件实践
【9月更文挑战第10天】在安卓开发领域,自定义View是提升用户体验和实现界面个性化的重要手段。本文将通过一个实际案例,展示如何在安卓项目中创建和使用自定义View组件,包括设计思路、实现步骤以及可能遇到的问题和解决方案。文章不仅提供了代码示例,还深入探讨了自定义View的性能优化技巧,旨在帮助开发者更好地掌握这一技能。
|
12天前
|
Android开发
Android中SurfaceView的双缓冲机制和普通View叠加问题解决办法
本文介绍了 Android 平台上的 SurfaceView,这是一种高效的图形渲染控件,尤其适用于视频播放、游戏和图形动画等场景。文章详细解释了其双缓冲机制,该机制通过前后缓冲区交换来减少图像闪烁,提升视觉体验。然而,SurfaceView 与普通 View 叠加时可能存在 Z-Order 不一致、同步问题及混合渲染难题。文中提供了使用 TextureView、调整 Z-Order 和创建自定义组合控件等多种解决方案。
46 9
|
16天前
|
Android开发 容器
Android经典实战之如何获取View和ViewGroup的中心点
本文介绍了在Android中如何获取`View`和`ViewGroup`的中心点坐标,包括计算相对坐标和屏幕上的绝对坐标,并提供了示例代码。特别注意在视图未完成测量时可能出现的宽高为0的问题及解决方案。
25 7
|
22天前
|
XML 搜索推荐 Android开发
安卓开发中的自定义View组件实践
【8月更文挑战第30天】探索Android世界,自定义View是提升应用界面的关键。本文以简洁的语言带你了解如何创建自定义View,从基础到高级技巧,一步步打造个性化的UI组件。
|
14天前
|
前端开发 搜索推荐 Android开发
探索安卓开发中的自定义视图##
【9月更文挑战第6天】 在安卓应用开发的世界里,自定义视图如同绘画艺术中的色彩,它们为界面设计增添了无限可能。通过掌握自定义视图的绘制技巧,开发者能够创造出既符合品牌形象又提升用户体验的独特界面元素。本文将深入浅出地介绍如何从零开始构建一个自定义视图,包括基础框架搭建、关键绘图方法实现、事件处理机制以及性能优化策略。准备好让你的安卓应用与众不同了吗?让我们开始吧! ##