让Android自带的Gallery实现多点缩放,拖动和边界回弹效果,效果流畅

本文主要是介绍让Android自带的Gallery实现多点缩放,拖动和边界回弹效果,效果流畅,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

让Android自带的Gallery实现多点缩放,拖动和边界回弹效果,效果流畅


公司项目要开发一个Android看漫画的软件。看了效果,需要滑动翻页,多点缩放,拖动等。但看每个效果在android上实现都不难,但要全部组合在一起实现就比较麻烦,研究了两天,动手写了几个效果对比,最终还是选择了Gallery来做。但系统自带的Gallery组件不支持对点缩放和拖动【它默认的拖动是翻页,我需要的移动定位图片】,并且当快速滑动时,Gallery是多张连续翻页,而我只需要每次翻页一张。查了Android部分源码。觉得重新Gallery和ImageView来实现。结果还是比较理想的。性能还是不错的,无论是拖动、翻页还是对点缩放都非常流畅。把大致思路放出来共享下,抛砖引玉,如果大家有更好的改进方法,可以共同讨论。文章末尾有完整的Demo源代码,童鞋们看仔细了,就不要再给我留言或发邮件问我要源码了【PS:以前写的文章在末尾都放了源码,但估计很多人都没耐心完整的看完,就发邮件来问我要源码,搞的我屁股都洗白白了,准备上床睡觉,又打开电脑回Email】。


下面是重写的Gallery的代码。注释基本都写上了。
MyGallery.java:
?[Copy to clipboard]View Code JAVA
/**
 * MyGallery.java
 * @version 1.0
 * @author Haven http://www.havenliu.com
 * @createTime 2011-12-9 下午03:42:53
 * android.widget.Gallery的子函数。此类很重要。建议仔细看
 */
package com.havenliu.demo;
 
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.widget.Gallery;
 
public class MyGallery extends Gallery {
private GestureDetector gestureScanner;
private MyImageView imageView;
 
public MyGallery(Context context) {
super(context);
 
}
 
public MyGallery(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
 
public MyGallery(Context context, AttributeSet attrs) {
super(context, attrs);
 
gestureScanner = new GestureDetector(new MySimpleGesture());
this.setOnTouchListener(new OnTouchListener() {
 
float baseValue;
float originalScale;
 
@Override
public boolean onTouch(View v, MotionEvent event) {
View view = MyGallery.this.getSelectedView();
if (view instanceof MyImageView) {
imageView = (MyImageView) view;
 
if (event.getAction() == MotionEvent.ACTION_DOWN) {
baseValue = 0;
originalScale = imageView.getScale();
}
if (event.getAction() == MotionEvent.ACTION_MOVE) {
if (event.getPointerCount() == 2) {
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
float value = (float) Math.sqrt(x * x + y * y);// 计算两点的距离
// System.out.println("value:" + value);
if (baseValue == 0) {
baseValue = value;
} else {
float scale = value / baseValue;// 当前两点间的距离除以手指落下时两点间的距离就是需要缩放的比例。
// scale the image
imageView.zoomTo(originalScale * scale, x + event.getX(1), y + event.getY(1));
 
}
}
}
}
return false;
}
 
});
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
View view = MyGallery.this.getSelectedView();
if (view instanceof MyImageView) {
imageView = (MyImageView) view;
 
float v[] = new float[9];
Matrix m = imageView.getImageMatrix();
m.getValues(v);
// 图片实时的上下左右坐标
float left, right;
// 图片的实时宽,高
float width, height;
width = imageView.getScale() * imageView.getImageWidth();
height = imageView.getScale() * imageView.getImageHeight();
// 一下逻辑为移动图片和滑动gallery换屏的逻辑。如果没对整个框架了解的非常清晰,改动以下的代码前请三思!!!!!!
if ((int) width <= Main.screenWidth && (int) height <= Main.screenHeight)// 如果图片当前大小<屏幕大小,直接处理滑屏事件 { super.onScroll(e1, e2, distanceX, distanceY); } else { left = v[Matrix.MTRANS_X]; right = left + width; Rect r = new Rect(); imageView.getGlobalVisibleRect(r); if (distanceX > 0)// 向左滑动
{
if (r.left > 0) {// 判断当前ImageView是否显示完全
super.onScroll(e1, e2, distanceX, distanceY);
} else if (right < Main.screenWidth) {
super.onScroll(e1, e2, distanceX, distanceY);
} else {
imageView.postTranslate(-distanceX, -distanceY);
}
} else if (distanceX < 0)// 向右滑动
{
if (r.right < Main.screenWidth) { super.onScroll(e1, e2, distanceX, distanceY); } else if (left > 0) {
super.onScroll(e1, e2, distanceX, distanceY);
} else {
imageView.postTranslate(-distanceX, -distanceY);
}
}
 
}
 
} else {
super.onScroll(e1, e2, distanceX, distanceY);
}
return false;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
return false;
}
 
@Override
public boolean onTouchEvent(MotionEvent event) {
gestureScanner.onTouchEvent(event);
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
// 判断上下边界是否越界
View view = MyGallery.this.getSelectedView();
if (view instanceof MyImageView) {
imageView = (MyImageView) view;
float width = imageView.getScale() * imageView.getImageWidth();
float height = imageView.getScale() * imageView.getImageHeight();
if ((int) width <= Main.screenWidth && (int) height <= Main.screenHeight)// 如果图片当前大小<屏幕大小,判断边界 { break; } float v[] = new float[9]; Matrix m = imageView.getImageMatrix(); m.getValues(v); float top = v[Matrix.MTRANS_Y]; float bottom = top + height; if (top > 0) {
imageView.postTranslateDur(-top, 200f);
}
Log.i("manga", "bottom:" + bottom);
if (bottom < Main.screenHeight) { imageView.postTranslateDur(Main.screenHeight - bottom, 200f); } } break; } return super.onTouchEvent(event); } private class MySimpleGesture extends SimpleOnGestureListener { // 按两下的第二下Touch down时触发 public boolean onDoubleTap(MotionEvent e) { View view = MyGallery.this.getSelectedView(); if (view instanceof MyImageView) { imageView = (MyImageView) view; if (imageView.getScale() > imageView.getScaleRate()) {
imageView.zoomTo(imageView.getScaleRate(), Main.screenWidth / 2, Main.screenHeight / 2, 200f);
// imageView.layoutToCenter();
} else {
imageView.zoomTo(1.0f, Main.screenWidth / 2, Main.screenHeight / 2, 200f);
}
 
} else {
 
}
// return super.onDoubleTap(e);
return true;
}
}
}
MyImageView.java:
?[Copy to clipboard]View Code JAVA
/**
 * MyImageView.java
 * @version 1.0
 * @author Haven  http://www.havenliu.com
 * @createTime 2011-12-9 下午03:12:30
 * 此类代码是根据android系统自带的ImageViewTouchBase代码修改
 */
package com.havenliu.demo;
 
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.widget.ImageView;
 
public class MyImageView extends ImageView {
@SuppressWarnings("unused")
private static final String TAG = "ImageViewTouchBase";
 
// This is the base transformation which is used to show the image
// initially. The current computation for this shows the image in
// it's entirety, letterboxing as needed. One could choose to
// show the image as cropped instead.
//
// This matrix is recomputed when we go from the thumbnail image to
// the full size image.
protected Matrix mBaseMatrix = new Matrix();
 
// This is the supplementary transformation which reflects what
// the user has done in terms of zooming and panning.
//
// This matrix remains the same when we go from the thumbnail image
// to the full size image.
protected Matrix mSuppMatrix = new Matrix();
 
// This is the final matrix which is computed as the concatentation
// of the base matrix and the supplementary matrix.
private final Matrix mDisplayMatrix = new Matrix();
 
// Temporary buffer used for getting the values out of a matrix.
private final float[] mMatrixValues = new float[9];
 
// The current bitmap being displayed.
// protected final RotateBitmap mBitmapDisplayed = new RotateBitmap(null);
protected Bitmap image = null;
 
int mThisWidth = -1, mThisHeight = -1;
 
float mMaxZoom = 2.0f;// 最大缩放比例
float mMinZoom ;// 最小缩放比例
 
private int imageWidth;// 图片的原始宽度
private int imageHeight;// 图片的原始高度
 
private float scaleRate;// 图片适应屏幕的缩放比例
 
public MyImageView(Context context, int imageWidth, int imageHeight) {
super(context);
this.imageHeight = imageHeight;
this.imageWidth = imageWidth;
init();
}
 
public MyImageView(Context context, AttributeSet attrs, int imageWidth, int imageHeight) {
super(context, attrs);
this.imageHeight = imageHeight;
this.imageWidth = imageWidth;
init();
}
 
/**
* 计算图片要适应屏幕需要缩放的比例
*/
private void arithScaleRate() {
float scaleWidth = Main.screenWidth / (float) imageWidth;
float scaleHeight = Main.screenHeight / (float) imageHeight;
scaleRate = Math.min(scaleWidth, scaleHeight);
}
 
public float getScaleRate() {
return scaleRate;
}
 
public int getImageWidth() {
return imageWidth;
}
 
public void setImageWidth(int imageWidth) {
this.imageWidth = imageWidth;
}
 
public int getImageHeight() {
return imageHeight;
}
 
public void setImageHeight(int imageHeight) {
this.imageHeight = imageHeight;
}
 
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
event.startTracking();
return true;
}
return super.onKeyDown(keyCode, event);
}
 
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking() && !event.isCanceled()) {
if (getScale() > 1.0f) {
// If we're zoomed in, pressing Back jumps out to show the
// entire image, otherwise Back returns the user to the gallery.
zoomTo(1.0f);
return true;
}
}
return super.onKeyUp(keyCode, event);
}
 
protected Handler mHandler = new Handler();
 
@Override
public void setImageBitmap(Bitmap bitmap) {
super.setImageBitmap(bitmap);
image = bitmap;
// 计算适应屏幕的比例
arithScaleRate();
//缩放到屏幕大小
zoomTo(scaleRate,Main.screenWidth / 2f, Main.screenHeight / 2f);
 
//居中
layoutToCenter();
 
// imageView.zoomTo(scaleRate, Main.screenWidth / 2, Main.screenHeight / 2
// center(true, true);
}
 
// Center as much as possible in one or both axis. Centering is
// defined as follows: if the image is scaled down below the
// view's dimensions then center it (literally). If the image
// is scaled larger than the view and is translated out of view
// then translate it back into view (i.e. eliminate black bars).
protected void center(boolean horizontal, boolean vertical) {
// if (mBitmapDisplayed.getBitmap() == null) {
// return;
// }
if (image == null) {
return;
}
 
Matrix m = getImageViewMatrix();
 
RectF rect = new RectF(0, 0, image.getWidth(), image.getHeight());
// RectF rect = new RectF(0, 0, imageWidth*getScale(), imageHeight*getScale());
 
m.mapRect(rect);
 
float height = rect.height();
float width = rect.width();
 
float deltaX = 0, deltaY = 0;
 
if (vertical) {
int viewHeight = getHeight();
if (height < viewHeight) { deltaY = (viewHeight - height) / 2 - rect.top; } else if (rect.top > 0) {
deltaY = -rect.top;
} else if (rect.bottom < viewHeight) {
deltaY = getHeight() - rect.bottom;
}
}
 
if (horizontal) {
int viewWidth = getWidth();
if (width < viewWidth) { deltaX = (viewWidth - width) / 2 - rect.left; } else if (rect.left > 0) {
deltaX = -rect.left;
} else if (rect.right < viewWidth) { deltaX = viewWidth - rect.right; } } postTranslate(deltaX, deltaY); setImageMatrix(getImageViewMatrix()); } private void init() { setScaleType(ImageView.ScaleType.MATRIX); } /** * 设置图片居中显示 */ public void layoutToCenter() { //正在显示的图片实际宽高 float width = imageWidth*getScale(); float height = imageHeight*getScale(); //空白区域宽高 float fill_width = Main.screenWidth - width; float fill_height = Main.screenHeight - height; //需要移动的距离 float tran_width = 0f; float tran_height = 0f; if(fill_width>0)
tran_width = fill_width/2;
if(fill_height>0)
tran_height = fill_height/2;
 
postTranslate(tran_width, tran_height);
setImageMatrix(getImageViewMatrix());
}
 
protected float getValue(Matrix matrix, int whichValue) {
matrix.getValues(mMatrixValues);
mMinZoom =( Main.screenWidth/2f)/imageWidth;
 
return mMatrixValues[whichValue];
}
 
// Get the scale factor out of the matrix.
protected float getScale(Matrix matrix) {
return getValue(matrix, Matrix.MSCALE_X);
}
 
protected float getScale() {
return getScale(mSuppMatrix);
}
 
// Combine the base matrix and the supp matrix to make the final matrix.
protected Matrix getImageViewMatrix() {
// The final matrix is computed as the concatentation of the base matrix
// and the supplementary matrix.
mDisplayMatrix.set(mBaseMatrix);
mDisplayMatrix.postConcat(mSuppMatrix);
return mDisplayMatrix;
}
 
static final float SCALE_RATE = 1.25F;
 
// Sets the maximum zoom, which is a scale relative to the base matrix. It
// is calculated to show the image at 400% zoom regardless of screen or
// image orientation. If in the future we decode the full 3 megapixel image,
// rather than the current 1024x768, this should be changed down to 200%.
protected float maxZoom() {
if (image == null) {
return 1F;
}
 
float fw = (float) image.getWidth() / (float) mThisWidth;
float fh = (float) image.getHeight() / (float) mThisHeight;
float max = Math.max(fw, fh) * 4;
return max;
}
 
protected void zoomTo(float scale, float centerX, float centerY) {
if (scale > mMaxZoom) {
scale = mMaxZoom;
} else if (scale < mMinZoom) {
scale = mMinZoom;
}
 
float oldScale = getScale();
float deltaScale = scale / oldScale;
 
mSuppMatrix.postScale(deltaScale, deltaScale, centerX, centerY);
setImageMatrix(getImageViewMatrix());
center(true, true);
}
 
protected void zoomTo(final float scale, final float centerX, final float centerY, final float durationMs) {
final float incrementPerMs = (scale - getScale()) / durationMs;
final float oldScale = getScale();
final long startTime = System.currentTimeMillis();
 
mHandler.post(new Runnable() {
public void run() {
long now = System.currentTimeMillis();
float currentMs = Math.min(durationMs, now - startTime);
float target = oldScale + (incrementPerMs * currentMs);
zoomTo(target, centerX, centerY);
if (currentMs < durationMs) { mHandler.post(this); } } }); } protected void zoomTo(float scale) { float cx = getWidth() / 2F; float cy = getHeight() / 2F; zoomTo(scale, cx, cy); } protected void zoomToPoint(float scale, float pointX, float pointY) { float cx = getWidth() / 2F; float cy = getHeight() / 2F; panBy(cx - pointX, cy - pointY); zoomTo(scale, cx, cy); } protected void zoomIn() { zoomIn(SCALE_RATE); } protected void zoomOut() { zoomOut(SCALE_RATE); } protected void zoomIn(float rate) { if (getScale() >= mMaxZoom) {
return; // Don't let the user zoom into the molecular level.
} else if (getScale() <= mMinZoom) {
return;
}
if (image == null) {
return;
}
 
float cx = getWidth() / 2F;
float cy = getHeight() / 2F;
 
mSuppMatrix.postScale(rate, rate, cx, cy);
setImageMatrix(getImageViewMatrix());
}
 
protected void zoomOut(float rate) {
if (image == null) {
return;
}
 
float cx = getWidth() / 2F;
float cy = getHeight() / 2F;
 
// Zoom out to at most 1x.
Matrix tmp = new Matrix(mSuppMatrix);
tmp.postScale(1F / rate, 1F / rate, cx, cy);
 
if (getScale(tmp) < 1F) {
mSuppMatrix.setScale(1F, 1F, cx, cy);
} else {
mSuppMatrix.postScale(1F / rate, 1F / rate, cx, cy);
}
setImageMatrix(getImageViewMatrix());
center(true, true);
}
 
public void postTranslate(float dx, float dy) {
mSuppMatrix.postTranslate(dx, dy);
setImageMatrix(getImageViewMatrix());
}
float _dy=0.0f;
protected void postTranslateDur( final float dy, final float durationMs) {
_dy=0.0f;
final float incrementPerMs = dy / durationMs;
final long startTime = System.currentTimeMillis();
mHandler.post(new Runnable() {
public void run() {
long now = System.currentTimeMillis();
float currentMs = Math.min(durationMs, now - startTime);
 
postTranslate(0, incrementPerMs*currentMs-_dy);
_dy=incrementPerMs*currentMs;
 
if (currentMs < durationMs) {
mHandler.post(this);
}
}
});
}
protected void panBy(float dx, float dy) {
postTranslate(dx, dy);
setImageMatrix(getImageViewMatrix());
}

}


http://www.havenliu.com/android/668.html

这篇关于让Android自带的Gallery实现多点缩放,拖动和边界回弹效果,效果流畅的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/688355

相关文章

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略 1. 特权模式限制2. 宿主机资源隔离3. 用户和组管理4. 权限提升控制5. SELinux配置 💖The Begin💖点点关注,收藏不迷路💖 Kubernetes的PodSecurityPolicy(PSP)是一个关键的安全特性,它在Pod创建之前实施安全策略,确保P

防近视护眼台灯什么牌子好?五款防近视效果好的护眼台灯推荐

在家里,灯具是属于离不开的家具,每个大大小小的地方都需要的照亮,所以一盏好灯是必不可少的,每个发挥着作用。而护眼台灯就起了一个保护眼睛,预防近视的作用。可以保护我们在学习,阅读的时候提供一个合适的光线环境,保护我们的眼睛。防近视护眼台灯什么牌子好?那我们怎么选择一个优秀的护眼台灯也是很重要,才能起到最大的护眼效果。下面五款防近视效果好的护眼台灯推荐: 一:六个推荐防近视效果好的护眼台灯的

怎么让1台电脑共享给7人同时流畅设计

在当今的创意设计与数字内容生产领域,图形工作站以其强大的计算能力、专业的图形处理能力和稳定的系统性能,成为了众多设计师、动画师、视频编辑师等创意工作者的必备工具。 设计团队面临资源有限,比如只有一台高性能电脑时,如何高效地让七人同时流畅地进行设计工作,便成为了一个亟待解决的问题。 一、硬件升级与配置 1.高性能处理器(CPU):选择多核、高线程的处理器,例如Intel的至强系列或AMD的Ry