在各种APP中,我们会经常看中都会涉及到一个图片预览的功能。研究了android的手势和滑动处理,自定义实现一个PhotoView。支持图片双击放大,拖动,放大边界控制,双指放大缩小等功能 。话不多说,先看效果图。
上图演示了图片的双击放大和缩小。项目我已经放大我的GitHub上,地址如下:
https://github.com/hugoca/PhotoView
本博文主要分析具体实现流程。
第一步 创建自定义View,初始化画笔和图片。
public class MyPhotoView extends View {
private static final float IMAGE_WIDTH=Utils.dpToPixel(300);
private static final float SCALE_FACTOR=1.5f;
private Bitmap mBitmap;
private Paint mPaint;
public MyPhotoView(Context context) {
super(context);
init(context);
}
public MyPhotoView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context);
}
public MyPhotoView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context){
mBitmap= Utils.getPhoto(getResources(), (int) IMAGE_WIDTH);
mPaint=new Paint(Paint.ANTI_ALIAS_FLAG);
}
}
工具类Util代码
工具类中提供两个方法, 一个是处理dp转换的,一个是获取图片Bitmap的。
public class Utils {
public static float dpToPixel(float dp) {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,
Resources.getSystem().getDisplayMetrics());
}
public static Bitmap getPhoto(Resources resources,int width){
BitmapFactory.Options options=new BitmapFactory.Options();
options.inJustDecodeBounds=true;
BitmapFactory.decodeResource(resources,R.drawable.photo,options);
options.inJustDecodeBounds=false;
options.inDensity=options.outWidth;
options.inTargetDensity=width;
return BitmapFactory.decodeResource(resources,R.drawable.photo,options);
}
}
第三步重写onSizeChanged方法
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
originalOffsetX=(getWidth()-mBitmap.getWidth())/2f;
originalOffsetY=(getHeight()-mBitmap.getHeight())/2f;
if((float)mBitmap.getWidth()/mBitmap.getHeight()>(float) getWidth()/getHeight()){
smallScale=(float) getWidth()/mBitmap.getWidth();
bigScale=(float)getHeight()/mBitmap.getHeight()*SCALE_FACTOR;
}else {
smallScale=(float)getHeight()/mBitmap.getHeight();
bigScale=(float) getWidth()/mBitmap.getWidth()*SCALE_FACTOR;
}
curScale=smallScale;
}
第四步 重写onDraw方法
重写onDraw方法进行图片的绘制
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
scaleFactor=(curScale-smallScale)/(bigScale-smallScale);
//当前放大比例为small时,scaleFactor=0 不偏移
//通过设置所放量来决定bitmap显示的偏移量
canvas.translate(offsetX*scaleFactor,offsetY*scaleFactor);
canvas.scale(curScale,curScale,getWidth()/2f,getHeight()/2f);
canvas.drawBitmap(mBitmap,originalOffsetX,originalOffsetY,mPaint);
}
private float getMaxWidth(){
return (mBitmap.getWidth()*bigScale-getWidth())/2;
}
private float getMaxHeight(){
return (mBitmap.getHeight()*bigScale-getHeight())/2;
}
第五步 添加手势控制
手势类中要处理的一个方法进行事件
1.将onDown事件返回值设置成true
2.处理双击事件,进行图片缩放
3.处理滑动事件
4.处理惯性滑动
class photoGestureDetector extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onDown(MotionEvent e) {
return true;
}
@Override
public boolean onDoubleTap(MotionEvent e) {
isEnLarge=!isEnLarge;
if(isEnLarge){
offsetX=0;
offsetY=0;
offsetX=(e.getX()-getWidth()/2f)-(e.getX()-getWidth()/2f)*bigScale/smallScale;
offsetY=(e.getY()-getHeight()/2f)-(e.getY()-getHeight()/2f)*bigScale/smallScale;
getScaleAnimation().start();
}else {
getScaleAnimation().reverse();
}
return super.onDoubleTap(e);
}
/**
*
* @param e1 手指按下
* @param e2 当前的
* @param distanceX 旧位置 - 新位置
* @param distanceY
* @return
*/
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
if(isEnLarge){
if(distanceX>0){
offsetX=Math.max(offsetX-distanceX,-getMaxWidth());
}else {
offsetX=Math.min(offsetX-distanceX,getMaxWidth());
}
if(distanceY>0){
offsetY=Math.max(offsetY-distanceY,-getMaxHeight());
}else {
offsetY=Math.min(offsetY-distanceY,getMaxHeight());
}
invalidate();
}
return super.onScroll(e1, e2, distanceX, distanceY);
}
@Override //惯性滑动
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
if(isEnLarge){
overScroller.fling((int)offsetX,(int)offsetY,
(int)velocityX,(int)velocityY,
-(int)getMaxWidth(),(int)getMaxWidth()
,-(int)getMaxHeight(),(int)getMaxHeight(),300,300);
postOnAnimation(new FingRunnable());
}
return super.onFling(e1, e2, velocityX, velocityY);
}
}
惯性滑动用到的runnable
class FingRunnable implements Runnable{
@Override
public void run() {
if (overScroller.computeScrollOffset()) {
offsetX=overScroller.getCurrX();
offsetY=overScroller.getCurrY();
invalidate();
postOnAnimation(this);
}
}
}
第六步 添加双指操作处理
class PhotoScakeGestrueListener implements ScaleGestureDetector.OnScaleGestureListener{
private float beginScale; //操作前的缩放比例
@Override
public boolean onScale(ScaleGestureDetector scaleGestureDetector) {
if((curScale>smallScale&&!isEnLarge)||(curScale==smallScale&&!isEnLarge)){
isEnLarge= true;
}
curScale=beginScale*scaleGestureDetector.getScaleFactor(); //缩放因子
invalidate();
return false;
}
@Override
public boolean onScaleBegin(ScaleGestureDetector scaleGestureDetector) {
beginScale=curScale;
return true;
}
@Override
public void onScaleEnd(ScaleGestureDetector scaleGestureDetector) {
}
}
到这里基本上自定义View已经实现了。为了方便实现,涉及到除了工具类都做的内部类实现,方便处理逻辑。最后添加相关类的手势的初始化
public class MyPhotoView extends View {
private static final float IMAGE_WIDTH=Utils.dpToPixel(300);
private static final float SCALE_FACTOR=1.5f;
private Bitmap mBitmap;
private Paint mPaint;
//图片起始位置偏移坐标
private float originalOffsetX;
private float originalOffsetY;
private float smallScale; //横向缩放填充
private float bigScale; //纵向缩放填充
private float curScale; //当前缩放
private boolean isEnLarge; //是否已经放大
private GestureDetector gestureDetector; //手势操作
private ObjectAnimator scaleAnimator; //处理缩放的动画
private OverScroller overScroller; //处理惯性滑动
private ScaleGestureDetector scaleGestureDetector;
private void init(Context context){
mBitmap=Utils.getPhoto(getResources(), (int) IMAGE_WIDTH);
mPaint=new Paint(Paint.ANTI_ALIAS_FLAG);
gestureDetector=new GestureDetector(context, new photoGestureDetector());
// 关闭长按响应
// gestureDetector.setIsLongpressEnabled(false);
overScroller=new OverScroller(context);
scaleGestureDetector=new ScaleGestureDetector(context,new PhotoScakeGestrueListener());
}
布局文件中添加
到这里自定义View便实现了,完整项目在github(MyPhotoView 地址)上,在项目中我还放了一个处理多指滑动的view。希望对你有所帮助,还望不吝点赞!