一个线程有几个handler?

答:作为应用层的类时,一个线程允许同时存在多个handler,但是都属于一个Looper; hander作为通信机制时,仅有一套统信机制looper,安卓与主线程通信的跨线程通信底层都是通过looper;

一个线程对应一个Looper,一个Looper一个MessageQueue;

线程间统信的原理是怎样的?

image-20230423105219078

image-20230423105246678

handler发送msg流程:handler.sendMessage()->handler.enquenueMessage->MessageQueue.enqueueMessage;

主线程取消息/处理消息流程:Activity,main()->Looper.loop()->MessageQueue.next()->handler.dispatchMessage->handler.hanleMessage();

线程间的通信原理是内存共享(MessageQueue共享);

Handler内存泄漏的原因?为什么其他内部类没有过这种问题?

1
2
3
4
5
6
7
8
//MainActivity.class
private Handler mHandler=new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
click(); //调用这个方法时默认为MainActivity.this.click();会持有MainActivity.this的对象;
}
}

handler在activity中使用时的持有链: statuc sThreadLocal->Looper->MessageQueue Message->Handler-MainActivity.this;

当handler执行delay消息时,jvm无法回收上面的持有链,导致内存泄漏;

1
2
3
解决方案:
1.handler加static关键字,然后外部方法才有弱引用,软引用;
2.activity销毁时释放所有handler的消息,如 mHandler.removeCallbacksAndMessages(null);

子线程多并发安全的使用handler:

1
2
3
4
HandlerThread handlerThread = new HandlerThread("threadName");
handlerThread.start();//HandlerThread中处理了looper同步和线程锁等问题;
Handler handler1 = new Handler(handlerThread.getLooper());
Handler handler2 = new Handler(handlerThread.getLooper());

message消息复用:

1
mHandler.obtainMessage();//清空消息后复用message对象,大量new Message会存在内存抖动;

方法1:使用KeyWordUtils

使用方法:

ScrollView中包含LinearLayout,改LinearLayout中可放置多个edittext

activity在配置文件中记得添加 android:windowSoftInputMode=”adjustResize|stateHidden”

img

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
//源码:
import android.annotation.SuppressLint;

import android.app.Activity;

import android.content.Context;

import android.graphics.Rect;

import android.view.View;

import android.view.ViewGroup;

import android.view.WindowManager;

import android.widget.ScrollView;

public class KeyWordUtils {

/**

\* 弹出键盘时滚动到界面指定位置

\* Created 2015-1-30 上午11:19:23

\* @param activity

\* 当前Activity

\* @param lyRootID

\* 所在根布局ID

\* @param vID

\* 要滚动到的控件ID

\* @param svID

\* 控件所在ScrollView ID

\* @author gpy

*/

@SuppressLint("NewApi")

public static void pullKeywordTop(final Activity activity,final int lyRootID,final int vID,final int svID,final int needHideId){

ViewGroup ly = (ViewGroup) activity.findViewById(lyRootID);

//获取屏幕高度,根据经验,输入法弹出高度一般在屏幕1/3到1/2之间
final int defaultHeight = ((WindowManager)activity.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getHeight();
final int mKeyHeight = defaultHeight/4;
ly.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right,
int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
//获取根布局前后高度差
int height = oldBottom-bottom;
ScrollView sv = (ScrollView)activity.findViewById(svID);

if(height>mKeyHeight) {//当高度差大于屏幕1/4,认为是输入法弹出变动,可能会有特殊机型会失败
activity.findViewById(needHideId).setVisibility(View.GONE);
final int lybottom = bottom;
sv.post(new Runnable() {//用post防止有时输入法会自动滚动覆盖我们手动滚动
@Override
public void run() {
ScrollView runSv = (ScrollView)activity.findViewById(svID);
//获取要滚动至的控件到屏幕顶部高度
View v = (View)activity.findViewById(vID);
int[] loca = new int[2];
////获取在整个屏幕内的绝对坐标,注意这个值是要从屏幕顶端算起,也就是包括了通知栏的高度。loca[0] x,loca[1] y
v.getLocationOnScreen(loca);

//这种通知栏高度获取方法必须在布局构建完毕后才能生效,否则获取为0
Rect frame = new Rect();
//getWindowVisibleDisplayFrame方法可以获取到程序显示的区域,包括标题栏,但不包括状态栏。
activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);
//获取状态栏高度
int statusBarHeight = frame.top;
// 要滚动的距离=控件距屏幕顶部距离+控件高度-输入法弹出后的activity高度-通知栏高度
int scrollHeight = loca[1] + v.getHeight() - lybottom - statusBarHeight;
if(scrollHeight>0){
runSv.scrollBy(0, scrollHeight);
}
}
});
}else if(-height>mKeyHeight){//当输入法收起,回滚回顶部
sv.scrollTo(0,0);
activity.findViewById(needHideId).setVisibility(View.VISIBLE);
}
}
});

}

}

方法2:

在activity的底部加一个占位的View,当页面能测量控件高度时,每次键盘弹出后,增加占位View的高度。

findViewById(R.id.ly_login).getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { Rect r = new Rect();//获取当前界面可视部分 OpenCunGuanActivity.this.getWindow().getDecorView().getWindowVisibleDisplayFrame(r);//获取屏幕的高度 int screenHeight = OpenCunGuanActivity.this.getWindow().getDecorView().getRootView().getHeight();//此处就是用来获取键盘的高度的, 在键盘没有弹出的时候 此高度为0键盘弹出的时候为一个正数 int heightDifference = screenHeight - r.bottom;CMLog.i(“info”,”软键盘高度:”+ heightDifference +” 屏幕高度”+ screenHeight +” 可视区域高度:”+ r.bottom+” marginTop”+ marginTop);// 在xml中设置hiddenView的高度,会导致部分界面超过一些低像素高度小的手机,已进入界面就可以滑动 if (r.bottom< screenHeight) { originalParams.height= heightDifference + marginTop *3/5;hiddenView.setLayoutParams(originalParams);} else { originalParams.height=0;hiddenView.setLayoutParams(originalParams);mSvContent.scrollTo(0, marginTop);} } });

转自:http://m.blog.csdn.net/u012764110/article/details/52223804

android调试debug快捷键

\1. 【Ctrl+Shift+B】:在当前行设置断点或取消设置的断点。

\2. 【F11】:调试最后一次执行的程序。

\3. 【Ctrl+F11】:运行最后一次执行的程序。

\4. 【F5】:跟踪到方法中,当程序执行到某方法时,可以按【F5】键跟踪到方法中。

\5. 【F6】:单步执行程序。

\6. 【F7】:执行完方法,返回到调用此方法的后一条语句。

\7. 【F8】:继续执行,到下一个断点或程序结束。

android studio大体为我们提供了6个功能区:

1、单步调试区

2、断点管理区

3、求值表达式

4、线程帧栈区

5、对象变量区

6、变量观察区

下面我们分别对这6个区域进行介绍

一、单步调试区

img

该区提供了调试的主要操作,主要有:Step over、step into、force step into、step out、drop frame。

1、Show Execution Point

img

点击该按钮,光标将定位到当前正在调试的位置.

2、Stem Over

img

(进入下一行,不会进入方法内部)

单步跳过,点击该按钮将导致程序向下执行一行。如果当前行是一个方法调用,此行调用的方法被执行完毕后再到下一行。(注意不会进入方法内部)

3、Step Into

img

(进入下一行,进入方法内部或者进入底层源码)

单步跳入,执行该操作将导致程序向下执行一行。如果该行有自定义的方法,则进入该方法内部继续执行,需要注意如果是类库中的方法,则不会进入方法内部。

4、Force Step Into

img

(手动进入方法内部,或者源码内部)

强制单步跳入,和step into功能类似,主要区别在于:如果当前行有任何方法,则不管该方法是我们自行定义还是类库提供的,都能跳入到方法内部继续执行

5、Drop Frame

img

中断执行,并返回到方法执行的初始点,在这个过程中该方法对应的栈帧会从栈中移除.换言之,如果该方法是被调用的,则返回到当前方法被调用处,并且所有上下文变量的值也恢复到该方法未执行时的状态。

6、Force Run to Cursor

img

很好用的一个功能,可以忽略所有的断点,跳转到当前光标所在的位置调试;假如我们现在在第8行有断点,第10行有断点,目前程序停留在第8行断点上,我们将光标定位到第9行,单击该按钮,程序将跑到第9行;

7、Evaluate expression

img

点击该按钮会在当前调试的语句处嵌入一个交互式解释器,在该解释器中,你可以执行任何你想要执行的表达式进行求值操作。假如我们当前断点处有一个result的返回值;我们单击该按钮会弹出一个对话框,在该对话框中我们可以对该result进行各种表达式操作;

二、断点管理区

img

1、Return

img

点击该按钮会停止目前的应用,并且重新启动.换言之,就是你想要重新调试时,可以使用该操作,嗯,就是重新来过的意思.

2、Resume Program

img

跳转到下一个断点处,可以理解为下一个断点;如果没有断点,则运行结束;

3、Stop

img

停止调试;结束运行;

4、View BreakPoints

img

单击该按钮将会进入断点管理页面,在这里你可以查看所有断点,管理或者配置断点的行为,如:删除,修改属性信息等;

5、Mute BreakPoints

img

该按钮用来禁用/启动所有断点,假如我们在某个断点处得到了我们想要的结果,并不想看其他后续断点可以点击该按钮禁用所有断点,然后程序会正常执行结束;

三、变量观察区

img

我们在调试的时候,希望看某个变量的值,所以我们只需要简单设置一下就可以在变量观察区看到该变量的值,如下所示

img

这里有一个技巧,如果我们在调试过程中,突然想要看看这个变量换一个值后的运行结果;可以在调试的过程中修改该变量的值,具体操作如下:

img

如上图所示,右击变量num2选择set value可以弹出对话框重新设置num2的值,如下所示

img

可以看出num2的值原先为10,我们修改为50,回车即可生效;

img

运行结果发生变化;

上面已经介绍了单点调试、变量管理和变量观察三个区域,接下来我们根据断点的分类来介绍其他区域;

断点的分类

断点是调试器的功能之一,可以让程序暂停在需要的地方,帮助我们进行分析程序的运行过程。

在Android Studio中,断点又被以下五类:

条件断点

日志断点

异常断点

方法断点

属性断点

1、条件断点

所谓的条件断点就是在特定条件发生的断点,也就是,我们可将某个断点设置为只对某种事件感兴趣,最典型的应用就是在列表循环中,我们希望在某特定的元素出现时暂停程序运行。假如我们有一个数组里面有1、2、3、4、5五个值,我们想在值等于3的时候停下来,可以设置条件断点;

img

右击断点,在弹出的对话框中设置相应的条件即可,我们运行一下看下效果

img

可以看到在num==3的时候,程序停了下来;

2、日志断点

很多时候我们调试的时候更多的是打印日志定位异常代码,缩小范围之后再使用断点解决问题;所以经常做的事情就是在代码里面添加日志信息,输出函数参数,返回信息,输出我们感兴趣的变量信息等。但是这样做的问题在于我们需要重新编译运行程序,并且添加了很多无谓的代码且不好管理,这个时候我们可以使用日志断点;该类型的断点不会使程序停下来,而是在输出我们要它输出的日志信息,然后继续执行。

举例说明:

img

同样是右击断点,在上图的对话框中进行设置我们来看一下运行效果:

img

是不是比你在代码中添加输出语句方便多了。

3、异常断点

在有些情况下,我们只对某些特定的异常感兴趣,或者我们只对异常感兴趣;我们希望只要程序发生异常程序就能断下来;这好像保存现场一样,这样就会留下的线索比较多,可以使我们快速的找到问题得根源;

举例说明,首先我们添加一个异常断点,单击

img

然后在弹出的对话框中进行如下设置

img

假如我们只关心空指针异常可以进行如下设置

img

选中空指针异常即可,我们人为设置一个空指针异常来看下运行效果:

img

图中的bt_ride是一个空值的Button,可以看到,当程序发生空指针异常后会将光标直接定位的发生异常的位置;

4、方法断点

传统的调试方式是以行为单位的,所谓单步调试;但是很多时候我们关心的是某个函数的参数,返回值;(回想一下我们使用日志的时候打印的最多的信息难道不是函数的参数和返回值吗?)使用方法断点,我们可以在函数级别进行调试;如果经常跳进跳出函数或者只对某个函数的参数感兴趣,这种类型的断点非常实用。具体使用方法有两种方式;最简单的是在你感兴趣的方法头那一行打上断点,这时候你会发现断点图标有点不一样,这就是方法断点了,如下图:

img

5、Field WatchPoint

有没有这样一种场景:你发现某个值莫名其妙滴不知道什么时候被谁给修改了,罪魁祸首是谁?那么我们怎么揪出这个修改我们值的捣蛋鬼呢?那就是这个 Field WatchPoint的功能了;使用它我们可以在某个Field被访问或者修改的时候让程序断下来;完美解决这个问题。我们可以直接在这个变量旁边单击就可以添加Field WatchPoint

img

我们可以右击从弹出的菜单中设置默认该变量被修改的时候断下来,也可以设置每次访问该值都断下来;

转自链接:https://www.jianshu.com/p/9fbf316582e3

效果图:

img

Github链接:https://github.com/boycy815/PinchImageView

使用:

1:自定义控件通过java代码:PinchImageView imageView =new PinchImageView(this);获得image对象,

2:直接在xml中调用下面的类,自定义控件的用法;


工具类:

package com.lzyi.tpm.utils;

import android.animation.ValueAnimator;

import android.content.Context;

import android.graphics.Canvas;

import android.graphics.Matrix;

import android.graphics.PointF;

import android.graphics.RectF;

import android.util.AttributeSet;

import android.view.GestureDetector;

import android.view.MotionEvent;

import android.widget.ImageView;

import java.util.ArrayList;

import java.util.LinkedList;

import java.util.List;

import java.util.Queue;

/**

* 手势图片控件

* @author clifford

*/

public class PinchImageViewextends ImageView {

////////////////////////////////配置参数////////////////////////////////

/**

* 图片缩放动画时间

*/

public static final int SCALE_ANIMATOR_DURATION =200;

/**

* 惯性动画衰减参数

*/

public static final float FLING_DAMPING_FACTOR =0.9f;

/**

* 图片最大放大比例

*/

private static final float MAX_SCALE =4f;

////////////////////////////////监听器////////////////////////////////

/**

* 外界点击事件

* @see #setOnClickListener(OnClickListener)

*/

private OnClickListenermOnClickListener;

/**

* 外界长按事件

* @see #setOnLongClickListener(OnLongClickListener)

*/

private OnLongClickListenermOnLongClickListener;

@Override

public void setOnClickListener(OnClickListener l) {

//默认的click会在任何点击情况下都会触发,所以搞成自己的

​ mOnClickListener = l;

}

@Override

public void setOnLongClickListener(OnLongClickListener l) {

//默认的long click会在任何长按情况下都会触发,所以搞成自己的

​ mOnLongClickListener = l;

}

////////////////////////////////公共状态获取////////////////////////////////

/**

* 手势状态:自由状态

* @see #getPinchMode()

*/

public static final int PINCH_MODE_FREE =0;

/**

* 手势状态:单指滚动状态

* @see #getPinchMode()

*/

public static final int PINCH_MODE_SCROLL =1;

/**

* 手势状态:双指缩放状态

* @see #getPinchMode()

*/

public static final int PINCH_MODE_SCALE =2;

/**

* 外层变换矩阵,如果是单位矩阵,那么图片是fit center状态

* @see #getOuterMatrix(Matrix)

* @see #outerMatrixTo(Matrix, long)

*/

private MatrixmOuterMatrix =new Matrix();

/**

* 矩形遮罩

* @see #getMask()

* @see #zoomMaskTo(RectF, long)

*/

private RectFmMask;

/**

* 当前手势状态

* @see #getPinchMode()

* @see #PINCH_MODE_FREE

* @see #PINCH_MODE_SCROLL

* @see #PINCH_MODE_SCALE

*/

private int mPinchMode =PINCH_MODE_FREE;

/**

* 获取外部变换矩阵.

* 外部变换矩阵记录了图片手势操作的最终结果,是相对于图片fit center状态的变换.

* 默认值为单位矩阵,此时图片为fit center状态.

* @param matrix 用于填充结果的对象

* @return 如果传了matrix参数则将matrix填充后返回,否则new一个填充返回

*/

public MatrixgetOuterMatrix(Matrix matrix) {

if (matrix ==null) {

matrix =new Matrix(mOuterMatrix);

​ }else {

matrix.set(mOuterMatrix);

​ }

return matrix;

}

/**

* 获取内部变换矩阵.

* 内部变换矩阵是原图到fit center状态的变换,当原图尺寸变化或者控件大小变化都会发生改变

* 当尚未布局或者原图不存在时,其值无意义.所以在调用前需要确保前置条件有效,否则将影响计算结果.

* @param matrix 用于填充结果的对象

* @return 如果传了matrix参数则将matrix填充后返回,否则new一个填充返回

*/

public MatrixgetInnerMatrix(Matrix matrix) {

if (matrix ==null) {

matrix =new Matrix();

​ }else {

matrix.reset();

​ }

if (isReady()) {

//原图大小

​ RectF tempSrc = MathUtils.rectFTake(0, 0, getDrawable().getIntrinsicWidth(), getDrawable().getIntrinsicHeight());

​ //控件大小

​ RectF tempDst = MathUtils.rectFTake(0, 0, getWidth(), getHeight());

​ //计算fit center矩阵

​ matrix.setRectToRect(tempSrc, tempDst, Matrix.ScaleToFit.CENTER);

​ //释放临时对象

​ MathUtils.rectFGiven(tempDst);

​ MathUtils.rectFGiven(tempSrc);

​ }

return matrix;

}

/**

* 获取图片总变换矩阵.

* 总变换矩阵为内部变换矩阵x外部变换矩阵,决定了原图到所见最终状态的变换

* 当尚未布局或者原图不存在时,其值无意义.所以在调用前需要确保前置条件有效,否则将影响计算结果.

* @param matrix 用于填充结果的对象

* @return 如果传了matrix参数则将matrix填充后返回,否则new一个填充返回

* @see #getOuterMatrix(Matrix)

* @see #getInnerMatrix(Matrix)

*/

public MatrixgetCurrentImageMatrix(Matrix matrix) {

//获取内部变换矩阵

​ matrix = getInnerMatrix(matrix);

​ //乘上外部变换矩阵

​ matrix.postConcat(mOuterMatrix);

​ return matrix;

}

/**

* 获取当前变换后的图片位置和尺寸

* 当尚未布局或者原图不存在时,其值无意义.所以在调用前需要确保前置条件有效,否则将影响计算结果.

* @param rectF 用于填充结果的对象

* @return 如果传了rectF参数则将rectF填充后返回,否则new一个填充返回

* @see #getCurrentImageMatrix(Matrix)

*/

public RectFgetImageBound(RectF rectF) {

if (rectF ==null) {

rectF =new RectF();

​ }else {

rectF.setEmpty();

​ }

if (!isReady()) {

return rectF;

​ }else {

//申请一个空matrix

​ Matrix matrix = MathUtils.matrixTake();

​ //获取当前总变换矩阵

​ getCurrentImageMatrix(matrix);

​ //对原图矩形进行变换得到当前显示矩形

​ rectF.set(0, 0, getDrawable().getIntrinsicWidth(), getDrawable().getIntrinsicHeight());

​ matrix.mapRect(rectF);

​ //释放临时matrix

​ MathUtils.matrixGiven(matrix);

​ return rectF;

​ }

}

/**

* 获取当前设置的mask

* @return 返回当前的mask对象副本,如果当前没有设置mask则返回null

*/

public RectFgetMask() {

if (mMask !=null) {

return new RectF(mMask);

​ }else {

return null;

​ }

}

/**

* 获取当前手势状态

* @see #PINCH_MODE_FREE

* @see #PINCH_MODE_SCROLL

* @see #PINCH_MODE_SCALE

*/

public int getPinchMode() {

return mPinchMode;

}

/**

* 与ViewPager结合的时候使用

* @param direction

* @return

*/

@Override

public boolean canScrollHorizontally(int direction) {

if (mPinchMode == PinchImageView.PINCH_MODE_SCALE) {

return true;

​ }

RectF bound = getImageBound(null);

​ if (bound ==null) {

return false;

​ }

if (bound.isEmpty()) {

return false;

​ }

if (direction >0) {

return bound.right > getWidth();

​ }else {

return bound.left <0;

​ }

}

/**

* 与ViewPager结合的时候使用

* @param direction

* @return

*/

@Override

public boolean canScrollVertically(int direction) {

if (mPinchMode == PinchImageView.PINCH_MODE_SCALE) {

return true;

​ }

RectF bound = getImageBound(null);

​ if (bound ==null) {

return false;

​ }

if (bound.isEmpty()) {

return false;

​ }

if (direction >0) {

return bound.bottom > getHeight();

​ }else {

return bound.top <0;

​ }

}

////////////////////////////////公共状态设置////////////////////////////////

/**

* 执行当前outerMatrix到指定outerMatrix渐变的动画

* 调用此方法会停止正在进行中的手势以及手势动画.

* 当duration为0时,outerMatrix值会被立即设置而不会启动动画.

* @param endMatrix 动画目标矩阵

* @param duration 动画持续时间

* @see #getOuterMatrix(Matrix)

*/

public void outerMatrixTo(Matrix endMatrix, long duration) {

if (endMatrix ==null) {

return;

​ }

//将手势设置为PINCH_MODE_FREE将停止后续手势的触发

​ mPinchMode =PINCH_MODE_FREE;

​ //停止所有正在进行的动画

​ cancelAllAnimator();

​ //如果时间不合法立即执行结果

​ if (duration <=0) {

mOuterMatrix.set(endMatrix);

​ dispatchOuterMatrixChanged();

​ invalidate();

​ }else {

//创建矩阵变化动画

​ mScaleAnimator =new ScaleAnimator(mOuterMatrix, endMatrix, duration);

​ mScaleAnimator.start();

​ }

}

/**

* 执行当前mask到指定mask的变化动画

* 调用此方法不会停止手势以及手势相关动画,但会停止正在进行的mask动画.

* 当前mask为null时,则不执行动画立即设置为目标mask.

* 当duration为0时,立即将当前mask设置为目标mask,不会执行动画.

* @param mask 动画目标mask

* @param duration 动画持续时间

* @see #getMask()

*/

public void zoomMaskTo(RectF mask, long duration) {

if (mask ==null) {

return;

​ }

//停止mask动画

​ if (mMaskAnimator !=null) {

mMaskAnimator.cancel();

​ mMaskAnimator =null;

​ }

//如果duration为0或者之前没有设置过mask,不执行动画,立即设置

​ if (duration <=0 ||mMask ==null) {

if (mMask ==null) {

mMask =new RectF();

​ }

mMask.set(mask);

​ invalidate();

​ }else {

//执行mask动画

​ mMaskAnimator =new MaskAnimator(mMask, mask, duration);

​ mMaskAnimator.start();

​ }

}

/**

* 重置所有状态

* 重置位置到fit center状态,清空mask,停止所有手势,停止所有动画.

* 但不清空drawable,以及事件绑定相关数据.

*/

public void reset() {

//重置位置到fit

​ mOuterMatrix.reset();

​ dispatchOuterMatrixChanged();

​ //清空mask

​ mMask =null;

​ //停止所有手势

​ mPinchMode =PINCH_MODE_FREE;

​ mLastMovePoint.set(0, 0);

​ mScaleCenter.set(0, 0);

​ mScaleBase =0;

​ //停止所有动画

​ if (mMaskAnimator !=null) {

mMaskAnimator.cancel();

​ mMaskAnimator =null;

​ }

cancelAllAnimator();

​ //重绘

​ invalidate();

}

////////////////////////////////对外广播事件////////////////////////////////

/**

* 外部矩阵变化事件通知监听器

*/

public interface OuterMatrixChangedListener {

/**

​ * 外部矩阵变化回调

​ *

​ * 外部矩阵的任何变化后都收到此回调.

​ * 外部矩阵变化后,总变化矩阵,图片的展示位置都将发生变化.

​ * @param pinchImageView

​ *

​ * @see #getOuterMatrix(Matrix)

​ * @see #getCurrentImageMatrix(Matrix)

​ * @see #getImageBound(RectF)

*/

​ void onOuterMatrixChanged(PinchImageView pinchImageView);

}

/**

* 所有OuterMatrixChangedListener监听列表

* @see #addOuterMatrixChangedListener(OuterMatrixChangedListener)

* @see #removeOuterMatrixChangedListener(OuterMatrixChangedListener)

*/

private ListmOuterMatrixChangedListeners;

/**

* 当mOuterMatrixChangedListeners被锁定不允许修改时,临时将修改写到这个副本中

* @see #mOuterMatrixChangedListeners

*/

private ListmOuterMatrixChangedListenersCopy;

/**

* mOuterMatrixChangedListeners的修改锁定

* 当进入dispatchOuterMatrixChanged方法时,被加1,退出前被减1

* @see #dispatchOuterMatrixChanged()

* @see #addOuterMatrixChangedListener(OuterMatrixChangedListener)

* @see #removeOuterMatrixChangedListener(OuterMatrixChangedListener)

*/

private int mDispatchOuterMatrixChangedLock;

/**

* 添加外部矩阵变化监听

* @param listener

*/

public void addOuterMatrixChangedListener(OuterMatrixChangedListener listener) {

if (listener ==null) {

return;

​ }

//如果监听列表没有被修改锁定直接将监听添加到监听列表

​ if (mDispatchOuterMatrixChangedLock ==0) {

if (mOuterMatrixChangedListeners ==null) {

mOuterMatrixChangedListeners =new ArrayList();

​ }

mOuterMatrixChangedListeners.add(listener);

​ }else {

//如果监听列表修改被锁定,那么尝试在监听列表副本上添加

​ //监听列表副本将会在锁定被解除时替换到监听列表里

​ if (mOuterMatrixChangedListenersCopy ==null) {

if (mOuterMatrixChangedListeners !=null) {

mOuterMatrixChangedListenersCopy =new ArrayList(mOuterMatrixChangedListeners);

​ }else {

mOuterMatrixChangedListenersCopy =new ArrayList();

​ }

}

mOuterMatrixChangedListenersCopy.add(listener);

​ }

}

/**

* 删除外部矩阵变化监听

* @param listener

*/

public void removeOuterMatrixChangedListener(OuterMatrixChangedListener listener) {

if (listener ==null) {

return;

​ }

//如果监听列表没有被修改锁定直接在监听列表数据结构上修改

​ if (mDispatchOuterMatrixChangedLock ==0) {

if (mOuterMatrixChangedListeners !=null) {

mOuterMatrixChangedListeners.remove(listener);

​ }

}else {

//如果监听列表被修改锁定,那么就在其副本上修改

​ //其副本将会在锁定解除时替换回监听列表

​ if (mOuterMatrixChangedListenersCopy ==null) {

if (mOuterMatrixChangedListeners !=null) {

mOuterMatrixChangedListenersCopy =new ArrayList(mOuterMatrixChangedListeners);

​ }

}

if (mOuterMatrixChangedListenersCopy !=null) {

mOuterMatrixChangedListenersCopy.remove(listener);

​ }

}

}

/**

* 触发外部矩阵修改事件

* 需要在每次给外部矩阵设置值时都调用此方法.

* @see #mOuterMatrix

*/

private void dispatchOuterMatrixChanged() {

if (mOuterMatrixChangedListeners ==null) {

return;

​ }

//增加锁

​ //这里之所以用计数器做锁定是因为可能在锁定期间又间接调用了此方法产生递归

​ //使用boolean无法判断递归结束

​ mDispatchOuterMatrixChangedLock++;

​ //在列表循环过程中不允许修改列表,否则将引发崩溃

​ for (OuterMatrixChangedListener listener :mOuterMatrixChangedListeners) {

listener.onOuterMatrixChanged(this);

​ }

//减锁

​ mDispatchOuterMatrixChangedLock–;

​ //如果是递归的情况,mDispatchOuterMatrixChangedLock可能大于1,只有减到0才能算列表的锁定解除

​ if (mDispatchOuterMatrixChangedLock ==0) {

//如果期间有修改列表,那么副本将不为null

​ if (mOuterMatrixChangedListenersCopy !=null) {

//将副本替换掉正式的列表

​ mOuterMatrixChangedListeners =mOuterMatrixChangedListenersCopy;

​ //清空副本

​ mOuterMatrixChangedListenersCopy =null;

​ }

}

}

////////////////////////////////用于重载定制////////////////////////////////

/**

* 获取图片最大可放大的比例

* 如果放大大于这个比例则不被允许.

* 在双手缩放过程中如果图片放大比例大于这个值,手指释放将回弹到这个比例.

* 在双击放大过程中不允许放大比例大于这个值.

* 覆盖此方法可以定制不同情况使用不同的最大可放大比例.

* @return 缩放比例

* @see #scaleEnd()

* @see #doubleTap(float, float)

*/

protected float getMaxScale() {

return MAX_SCALE;

}

/**

* 计算双击之后图片接下来应该被缩放的比例

* 如果值大于getMaxScale或者小于fit center尺寸,则实际使用取边界值.

* 通过覆盖此方法可以定制不同的图片被双击时使用不同的放大策略.

* @param innerScale 当前内部矩阵的缩放值

* @param outerScale 当前外部矩阵的缩放值

* @return 接下来的缩放比例

* @see #doubleTap(float, float)

* @see #getMaxScale()

*/

protected float calculateNextScale(float innerScale, float outerScale) {

float currentScale = innerScale * outerScale;

​ if (currentScale

return MAX_SCALE;

​ }else {

return innerScale;

​ }

}

////////////////////////////////初始化////////////////////////////////

public PinchImageView(Context context) {

super(context);

​ initView();

}

public PinchImageView(Context context, AttributeSet attrs) {

super(context, attrs);

​ initView();

}

public PinchImageView(Context context, AttributeSet attrs, int defStyle) {

super(context, attrs, defStyle);

​ initView();

}

private void initView() {

//强制设置图片scaleType为matrix

​ super.setScaleType(ScaleType.MATRIX);

}

//不允许设置scaleType,只能用内部设置的matrix

@Override

public void setScaleType(ScaleType scaleType) {}

////////////////////////////////绘制////////////////////////////////

@Override

protected void onDraw(Canvas canvas) {

//在绘制前设置变换矩阵

​ if (isReady()) {

Matrix matrix = MathUtils.matrixTake();

​ setImageMatrix(getCurrentImageMatrix(matrix));

​ MathUtils.matrixGiven(matrix);

​ }

//对图像做遮罩处理

​ if (mMask !=null) {

canvas.save();

​ canvas.clipRect(mMask);

​ super.onDraw(canvas);

​ canvas.restore();

​ }else {

super.onDraw(canvas);

​ }

}

////////////////////////////////有效性判断////////////////////////////////

/**

* 判断当前情况是否能执行手势相关计算

* 包括:是否有图片,图片是否有尺寸,控件是否有尺寸.

* @return 是否能执行手势相关计算

*/

private boolean isReady() {

return getDrawable() !=null && getDrawable().getIntrinsicWidth() >0 && getDrawable().getIntrinsicHeight() >0

​ && getWidth() >0 && getHeight() >0;

}

////////////////////////////////mask动画处理////////////////////////////////

/**

* mask修改的动画

* 和图片的动画相互独立.

* @see #zoomMaskTo(RectF, long)

*/

private MaskAnimatormMaskAnimator;

/**

* mask变换动画

* 将mask从一个rect动画到另外一个rect

*/

private class MaskAnimatorextends ValueAnimatorimplements ValueAnimator.AnimatorUpdateListener {

/**

​ * 开始mask

*/

​ private float[]mStart =new float[4];

​ /**

​ * 结束mask

*/

​ private float[]mEnd =new float[4];

​ /**

​ * 中间结果mask

*/

​ private float[]mResult =new float[4];

​ /**

​ * 创建mask变换动画

​ *

​ * @param start 动画起始状态

​ * @param end 动画终点状态

​ * @param duration 动画持续时间

​ */

​ public MaskAnimator(RectF start, RectF end, long duration) {

super();

​ setFloatValues(0, 1f);

​ setDuration(duration);

​ addUpdateListener(this);

​ //将起点终点拷贝到数组方便计算

​ mStart[0] = start.left;

​ mStart[1] = start.top;

​ mStart[2] = start.right;

​ mStart[3] = start.bottom;

​ mEnd[0] = end.left;

​ mEnd[1] = end.top;

​ mEnd[2] = end.right;

​ mEnd[3] = end.bottom;

​ }

@Override

​ public void onAnimationUpdate(ValueAnimator animation) {

//获取动画进度,0-1范围

​ float value = (Float) animation.getAnimatedValue();

​ //根据进度对起点终点之间做插值

​ for (int i =0; i <4; i++) {

mResult[i] =mStart[i] + (mEnd[i] -mStart[i]) * value;

​ }

//期间mask有可能被置空了,所以判断一下

​ if (mMask ==null) {

mMask =new RectF();

​ }

//设置新的mask并绘制

​ mMask.set(mResult[0], mResult[1], mResult[2], mResult[3]);

​ invalidate();

​ }

}

////////////////////////////////手势动画处理////////////////////////////////

/**

* 在单指模式下:

* 记录上一次手指的位置,用于计算新的位置和上一次位置的差值.

* 双指模式下:

* 记录两个手指的中点,作为和mScaleCenter绑定的点.

* 这个绑定可以保证mScaleCenter无论如何都会跟随这个中点.

* @see #mScaleCenter

* @see #scale(PointF, float, float, PointF)

* @see #scaleEnd()

*/

private PointFmLastMovePoint =new PointF();

/**

* 缩放模式下图片的缩放中点.

* 为其指代的点经过innerMatrix变换之后的值.

* 其指代的点在手势过程中始终跟随mLastMovePoint.

* 通过双指缩放时,其为缩放中心点.

* @see #saveScaleContext(float, float, float, float)

* @see #mLastMovePoint

* @see #scale(PointF, float, float, PointF)

*/

private PointFmScaleCenter =new PointF();

/**

* 缩放模式下的基础缩放比例

* 为外层缩放值除以开始缩放时两指距离.

* 其值乘上最新的两指之间距离为最新的图片缩放比例.

* @see #saveScaleContext(float, float, float, float)

* @see #scale(PointF, float, float, PointF)

*/

private float mScaleBase =0;

/**

* 图片缩放动画

* 缩放模式把图片的位置大小超出限制之后触发.

* 双击图片放大或缩小时触发.

* 手动调用outerMatrixTo触发.

* @see #scaleEnd()

* @see #doubleTap(float, float)

* @see #outerMatrixTo(Matrix, long)

*/

private ScaleAnimatormScaleAnimator;

/**

* 滑动产生的惯性动画

* @see #fling(float, float)

*/

private FlingAnimatormFlingAnimator;

/**

* 常用手势处理

* 在onTouchEvent末尾被执行.

*/

private GestureDetectormGestureDetector =new GestureDetector(PinchImageView.this.getContext(), new GestureDetector.SimpleOnGestureListener() {

public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {

//只有在单指模式结束之后才允许执行fling

​ if (mPinchMode ==PINCH_MODE_FREE && !(mScaleAnimator !=null &&mScaleAnimator.isRunning())) {

fling(velocityX, velocityY);

​ }

return true;

​ }

public void onLongPress(MotionEvent e) {

//触发长按

​ if (mOnLongClickListener !=null) {

mOnLongClickListener.onLongClick(PinchImageView.this);

​ }

}

public boolean onDoubleTap(MotionEvent e) {

//当手指快速第二次按下触发,此时必须是单指模式才允许执行doubleTap

​ if (mPinchMode ==PINCH_MODE_SCROLL && !(mScaleAnimator !=null &&mScaleAnimator.isRunning())) {

doubleTap(e.getX(), e.getY());

​ }

return true;

​ }

public boolean onSingleTapConfirmed(MotionEvent e) {

//触发点击

​ if (mOnClickListener !=null) {

mOnClickListener.onClick(PinchImageView.this);

​ }

return true;

​ }

});

@Override

public boolean onTouchEvent(MotionEvent event) {

super.onTouchEvent(event);

​ int action = event.getAction() & MotionEvent.ACTION_MASK;

​ //最后一个点抬起或者取消,结束所有模式

​ if(action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {

//如果之前是缩放模式,还需要触发一下缩放结束动画

​ if (mPinchMode ==PINCH_MODE_SCALE) {

scaleEnd();

​ }

mPinchMode =PINCH_MODE_FREE;

​ }else if (action == MotionEvent.ACTION_POINTER_UP) {

//多个手指情况下抬起一个手指,此时需要是缩放模式才触发

​ if (mPinchMode ==PINCH_MODE_SCALE) {

//抬起的点如果大于2,那么缩放模式还有效,但是有可能初始点变了,重新测量初始点

​ if (event.getPointerCount() >2) {

//如果还没结束缩放模式,但是第一个点抬起了,那么让第二个点和第三个点作为缩放控制点

​ if (event.getAction() >>8 ==0) {

saveScaleContext(event.getX(1), event.getY(1), event.getX(2), event.getY(2));

​ //如果还没结束缩放模式,但是第二个点抬起了,那么让第一个点和第三个点作为缩放控制点

​ }else if (event.getAction() >>8 ==1) {

saveScaleContext(event.getX(0), event.getY(0), event.getX(2), event.getY(2));

​ }

}

//如果抬起的点等于2,那么此时只剩下一个点,也不允许进入单指模式,因为此时可能图片没有在正确的位置上

​ }

//第一个点按下,开启滚动模式,记录开始滚动的点

​ }else if (action == MotionEvent.ACTION_DOWN) {

//在矩阵动画过程中不允许启动滚动模式

​ if (!(mScaleAnimator !=null &&mScaleAnimator.isRunning())) {

//停止所有动画

​ cancelAllAnimator();

​ //切换到滚动模式

​ mPinchMode =PINCH_MODE_SCROLL;

​ //保存触发点用于move计算差值

​ mLastMovePoint.set(event.getX(), event.getY());

​ }

//非第一个点按下,关闭滚动模式,开启缩放模式,记录缩放模式的一些初始数据

​ }else if (action == MotionEvent.ACTION_POINTER_DOWN) {

//停止所有动画

​ cancelAllAnimator();

​ //切换到缩放模式

​ mPinchMode =PINCH_MODE_SCALE;

​ //保存缩放的两个手指

​ saveScaleContext(event.getX(0), event.getY(0), event.getX(1), event.getY(1));

​ }else if (action == MotionEvent.ACTION_MOVE) {

if (!(mScaleAnimator !=null &&mScaleAnimator.isRunning())) {

//在滚动模式下移动

​ if (mPinchMode ==PINCH_MODE_SCROLL) {

//每次移动产生一个差值累积到图片位置上

​ scrollBy(event.getX() -mLastMovePoint.x, event.getY() -mLastMovePoint.y);

​ //记录新的移动点

​ mLastMovePoint.set(event.getX(), event.getY());

​ //在缩放模式下移动

​ }else if (mPinchMode ==PINCH_MODE_SCALE && event.getPointerCount() >1) {

//两个缩放点间的距离

​ float distance = MathUtils.getDistance(event.getX(0), event.getY(0), event.getX(1), event.getY(1));

​ //保存缩放点中点

​ float[] lineCenter = MathUtils.getCenterPoint(event.getX(0), event.getY(0), event.getX(1), event.getY(1));

​ mLastMovePoint.set(lineCenter[0], lineCenter[1]);

​ //处理缩放

​ scale(mScaleCenter, mScaleBase, distance, mLastMovePoint);

​ }

}

}

//无论如何都处理各种外部手势

​ mGestureDetector.onTouchEvent(event);

return true;

}

/**

* 让图片移动一段距离

* 不能移动超过可移动范围,超过了就到可移动范围边界为止.

* @param xDiff 移动距离

* @param yDiff 移动距离

* @return 是否改变了位置

*/

private boolean scrollBy(float xDiff, float yDiff) {

if (!isReady()) {

return false;

​ }

//原图方框

​ RectF bound = MathUtils.rectFTake();

​ getImageBound(bound);

​ //控件大小

​ float displayWidth = getWidth();

​ float displayHeight = getHeight();

​ //如果当前图片宽度小于控件宽度,则不能移动

​ if (bound.right - bound.left < displayWidth) {

xDiff =0;

​ //如果图片左边在移动后超出控件左边

​ }else if (bound.left + xDiff >0) {

//如果在移动之前是没超出的,计算应该移动的距离

​ if (bound.left <0) {

xDiff = -bound.left;

​ //否则无法移动

​ }else {

xDiff =0;

​ }

//如果图片右边在移动后超出控件右边

​ }else if (bound.right + xDiff < displayWidth) {

//如果在移动之前是没超出的,计算应该移动的距离

​ if (bound.right > displayWidth) {

xDiff = displayWidth - bound.right;

​ //否则无法移动

​ }else {

xDiff =0;

​ }

}

//以下同理

​ if (bound.bottom - bound.top < displayHeight) {

yDiff =0;

​ }else if (bound.top + yDiff >0) {

if (bound.top <0) {

yDiff = -bound.top;

​ }else {

yDiff =0;

​ }

}else if (bound.bottom + yDiff < displayHeight) {

if (bound.bottom > displayHeight) {

yDiff = displayHeight - bound.bottom;

​ }else {

yDiff =0;

​ }

}

MathUtils.rectFGiven(bound);

​ //应用移动变换

​ mOuterMatrix.postTranslate(xDiff, yDiff);

​ dispatchOuterMatrixChanged();

​ //触发重绘

​ invalidate();

​ //检查是否有变化

​ if (xDiff !=0 || yDiff !=0) {

return true;

​ }else {

return false;

​ }

}

/**

* 记录缩放前的一些信息

* 保存基础缩放值.

* 保存图片缩放中点.

* @param x1 缩放第一个手指

* @param y1 缩放第一个手指

* @param x2 缩放第二个手指

* @param y2 缩放第二个手指

*/

private void saveScaleContext(float x1, float y1, float x2, float y2) {

//记录基础缩放值,其中图片缩放比例按照x方向来计算

​ //理论上图片应该是等比的,x和y方向比例相同

​ //但是有可能外部设定了不规范的值.

​ //但是后续的scale操作会将xy不等的缩放值纠正,改成和x方向相同

​ mScaleBase = MathUtils.getMatrixScale(mOuterMatrix)[0] / MathUtils.getDistance(x1, y1, x2, y2);

​ //两手指的中点在屏幕上落在了图片的某个点上,图片上的这个点在经过总矩阵变换后和手指中点相同

​ //现在我们需要得到图片上这个点在图片是fit center状态下在屏幕上的位置

​ //因为后续的计算都是基于图片是fit center状态下进行变换

​ //所以需要把两手指中点除以外层变换矩阵得到mScaleCenter

​ float[] center = MathUtils.inverseMatrixPoint(MathUtils.getCenterPoint(x1, y1, x2, y2), mOuterMatrix);

​ mScaleCenter.set(center[0], center[1]);

}

/**

* 对图片按照一些手势信息进行缩放

* @param scaleCenter mScaleCenter

* @param scaleBase mScaleBase

* @param distance 手指两点之间距离

* @param lineCenter 手指两点之间中点

* @see #mScaleCenter

* @see #mScaleBase

*/

private void scale(PointF scaleCenter, float scaleBase, float distance, PointF lineCenter) {

if (!isReady()) {

return;

​ }

//计算图片从fit center状态到目标状态的缩放比例

​ float scale = scaleBase * distance;

​ Matrix matrix = MathUtils.matrixTake();

​ //按照图片缩放中心缩放,并且让缩放中心在缩放点中点上

​ matrix.postScale(scale, scale, scaleCenter.x, scaleCenter.y);

​ //让图片的缩放中点跟随手指缩放中点

​ matrix.postTranslate(lineCenter.x - scaleCenter.x, lineCenter.y - scaleCenter.y);

​ //应用变换

​ mOuterMatrix.set(matrix);

​ MathUtils.matrixGiven(matrix);

​ dispatchOuterMatrixChanged();

​ //重绘

​ invalidate();

}

/**

* 双击后放大或者缩小

* 将图片缩放比例缩放到nextScale指定的值.

* 但nextScale值不能大于最大缩放值不能小于fit center情况下的缩放值.

* 将双击的点尽量移动到控件中心.

* @param x 双击的点

* @param y 双击的点

* @see #calculateNextScale(float, float)

* @see #getMaxScale()

*/

private void doubleTap(float x, float y) {

if (!isReady()) {

return;

​ }

//获取第一层变换矩阵

​ Matrix innerMatrix = MathUtils.matrixTake();

​ getInnerMatrix(innerMatrix);

​ //当前总的缩放比例

​ float innerScale = MathUtils.getMatrixScale(innerMatrix)[0];

​ float outerScale = MathUtils.getMatrixScale(mOuterMatrix)[0];

​ float currentScale = innerScale * outerScale;

​ //控件大小

​ float displayWidth = getWidth();

​ float displayHeight = getHeight();

​ //最大放大大小

​ float maxScale = getMaxScale();

​ //接下来要放大的大小

​ float nextScale = calculateNextScale(innerScale, outerScale);

​ //如果接下来放大大于最大值或者小于fit center值,则取边界

​ if (nextScale > maxScale) {

nextScale = maxScale;

​ }

if (nextScale < innerScale) {

nextScale = innerScale;

​ }

//开始计算缩放动画的结果矩阵

​ Matrix animEnd = MathUtils.matrixTake(mOuterMatrix);

​ //计算还需缩放的倍数

​ animEnd.postScale(nextScale / currentScale, nextScale / currentScale, x, y);

​ //将放大点移动到控件中心

​ animEnd.postTranslate(displayWidth /2f - x, displayHeight /2f - y);

​ //得到放大之后的图片方框

​ Matrix testMatrix = MathUtils.matrixTake(innerMatrix);

​ testMatrix.postConcat(animEnd);

​ RectF testBound = MathUtils.rectFTake(0, 0, getDrawable().getIntrinsicWidth(), getDrawable().getIntrinsicHeight());

​ testMatrix.mapRect(testBound);

​ //修正位置

​ float postX =0;

​ float postY =0;

​ if (testBound.right - testBound.left < displayWidth) {

postX = displayWidth /2f - (testBound.right + testBound.left) /2f;

​ }else if (testBound.left >0) {

postX = -testBound.left;

​ }else if (testBound.right < displayWidth) {

postX = displayWidth - testBound.right;

​ }

if (testBound.bottom - testBound.top < displayHeight) {

postY = displayHeight /2f - (testBound.bottom + testBound.top) /2f;

​ }else if (testBound.top >0) {

postY = -testBound.top;

​ }else if (testBound.bottom < displayHeight) {

postY = displayHeight - testBound.bottom;

​ }

//应用修正位置

​ animEnd.postTranslate(postX, postY);

​ //清理当前可能正在执行的动画

​ cancelAllAnimator();

​ //启动矩阵动画

​ mScaleAnimator =new ScaleAnimator(mOuterMatrix, animEnd);

​ mScaleAnimator.start();

​ //清理临时变量

​ MathUtils.rectFGiven(testBound);

​ MathUtils.matrixGiven(testMatrix);

​ MathUtils.matrixGiven(animEnd);

​ MathUtils.matrixGiven(innerMatrix);

}

/**

* 当缩放操作结束动画

* 如果图片超过边界,找到最近的位置动画恢复.

* 如果图片缩放尺寸超过最大值或者最小值,找到最近的值动画恢复.

*/

private void scaleEnd() {

if (!isReady()) {

return;

​ }

//是否修正了位置

​ boolean change =false;

​ //获取图片整体的变换矩阵

​ Matrix currentMatrix = MathUtils.matrixTake();

​ getCurrentImageMatrix(currentMatrix);

​ //整体缩放比例

​ float currentScale = MathUtils.getMatrixScale(currentMatrix)[0];

​ //第二层缩放比例

​ float outerScale = MathUtils.getMatrixScale(mOuterMatrix)[0];

​ //控件大小

​ float displayWidth = getWidth();

​ float displayHeight = getHeight();

​ //最大缩放比例

​ float maxScale = getMaxScale();

​ //比例修正

​ float scalePost =1f;

​ //位置修正

​ float postX =0;

​ float postY =0;

​ //如果整体缩放比例大于最大比例,进行缩放修正

​ if (currentScale > maxScale) {

scalePost = maxScale / currentScale;

​ }

//如果缩放修正后整体导致第二层缩放小于1(就是图片比fit center状态还小),重新修正缩放

​ if (outerScale * scalePost <1f) {

scalePost =1f / outerScale;

​ }

//如果缩放修正不为1,说明进行了修正

​ if (scalePost !=1f) {

change =true;

​ }

//尝试根据缩放点进行缩放修正

​ Matrix testMatrix = MathUtils.matrixTake(currentMatrix);

​ testMatrix.postScale(scalePost, scalePost, mLastMovePoint.x, mLastMovePoint.y);

​ RectF testBound = MathUtils.rectFTake(0, 0, getDrawable().getIntrinsicWidth(), getDrawable().getIntrinsicHeight());

​ //获取缩放修正后的图片方框

​ testMatrix.mapRect(testBound);

​ //检测缩放修正后位置有无超出,如果超出进行位置修正

​ if (testBound.right - testBound.left < displayWidth) {

postX = displayWidth /2f - (testBound.right + testBound.left) /2f;

​ }else if (testBound.left >0) {

postX = -testBound.left;

​ }else if (testBound.right < displayWidth) {

postX = displayWidth - testBound.right;

​ }

if (testBound.bottom - testBound.top < displayHeight) {

postY = displayHeight /2f - (testBound.bottom + testBound.top) /2f;

​ }else if (testBound.top >0) {

postY = -testBound.top;

​ }else if (testBound.bottom < displayHeight) {

postY = displayHeight - testBound.bottom;

​ }

//如果位置修正不为0,说明进行了修正

​ if (postX !=0 || postY !=0) {

change =true;

​ }

//只有有执行修正才执行动画

​ if (change) {

//计算结束矩阵

​ Matrix animEnd = MathUtils.matrixTake(mOuterMatrix);

​ animEnd.postScale(scalePost, scalePost, mLastMovePoint.x, mLastMovePoint.y);

​ animEnd.postTranslate(postX, postY);

​ //清理当前可能正在执行的动画

​ cancelAllAnimator();

​ //启动矩阵动画

​ mScaleAnimator =new ScaleAnimator(mOuterMatrix, animEnd);

​ mScaleAnimator.start();

​ //清理临时变量

​ MathUtils.matrixGiven(animEnd);

​ }

//清理临时变量

​ MathUtils.rectFGiven(testBound);

​ MathUtils.matrixGiven(testMatrix);

​ MathUtils.matrixGiven(currentMatrix);

}

/**

* 执行惯性动画

* 动画在遇到不能移动就停止.

* 动画速度衰减到很小就停止.

* 其中参数速度单位为 像素/秒

* @param vx x方向速度

* @param vy y方向速度

*/

private void fling(float vx, float vy) {

if (!isReady()) {

return;

​ }

//清理当前可能正在执行的动画

​ cancelAllAnimator();

​ //创建惯性动画

​ //FlingAnimator单位为 像素/帧,一秒60帧

​ mFlingAnimator =new FlingAnimator(vx /60f, vy /60f);

​ mFlingAnimator.start();

}

/**

* 停止所有手势动画

*/

private void cancelAllAnimator() {

if (mScaleAnimator !=null) {

mScaleAnimator.cancel();

​ mScaleAnimator =null;

​ }

if (mFlingAnimator !=null) {

mFlingAnimator.cancel();

​ mFlingAnimator =null;

​ }

}

/**

* 惯性动画

* 速度逐渐衰减,每帧速度衰减为原来的FLING_DAMPING_FACTOR,当速度衰减到小于1时停止.

* 当图片不能移动时,动画停止.

*/

private class FlingAnimatorextends ValueAnimatorimplements ValueAnimator.AnimatorUpdateListener {

/**

​ * 速度向量

​ */

​ private float[]mVector;

​ /**

​ * 创建惯性动画

​ *

​ * 参数单位为 像素/帧

​ *

​ * @param vectorX 速度向量

​ * @param vectorY 速度向量

​ */

​ public FlingAnimator(float vectorX, float vectorY) {

super();

​ setFloatValues(0, 1f);

​ setDuration(1000000);

​ addUpdateListener(this);

​ mVector =new float[]{vectorX, vectorY};

​ }

@Override

​ public void onAnimationUpdate(ValueAnimator animation) {

//移动图像并给出结果

​ boolean result = scrollBy(mVector[0], mVector[1]);

​ //衰减速度

​ mVector[0] *=FLING_DAMPING_FACTOR;

​ mVector[1] *=FLING_DAMPING_FACTOR;

​ //速度太小或者不能移动了就结束

​ if (!result || MathUtils.getDistance(0, 0, mVector[0], mVector[1]) <1f) {

animation.cancel();

​ }

}

}

/**

* 缩放动画

* 在给定时间内从一个矩阵的变化逐渐动画到另一个矩阵的变化

*/

private class ScaleAnimatorextends ValueAnimatorimplements ValueAnimator.AnimatorUpdateListener {

/**

​ * 开始矩阵

​ */

​ private float[]mStart =new float[9];

​ /**

​ * 结束矩阵

​ */

​ private float[]mEnd =new float[9];

​ /**

​ * 中间结果矩阵

​ */

​ private float[]mResult =new float[9];

​ /**

​ * 构建一个缩放动画

​ *

​ * 从一个矩阵变换到另外一个矩阵

​ *

​ * @param start 开始矩阵

​ * @param end 结束矩阵

​ */

​ public ScaleAnimator(Matrix start, Matrix end) {

this(start, end, SCALE_ANIMATOR_DURATION);

​ }

/**

​ * 构建一个缩放动画

​ *

​ * 从一个矩阵变换到另外一个矩阵

​ *

​ * @param start 开始矩阵

​ * @param end 结束矩阵

​ * @param duration 动画时间

​ */

​ public ScaleAnimator(Matrix start, Matrix end, long duration) {

super();

​ setFloatValues(0, 1f);

​ setDuration(duration);

​ addUpdateListener(this);

​ start.getValues(mStart);

​ end.getValues(mEnd);

​ }

@Override

​ public void onAnimationUpdate(ValueAnimator animation) {

//获取动画进度

​ float value = (Float) animation.getAnimatedValue();

​ //根据动画进度计算矩阵中间插值

​ for (int i =0; i <9; i++) {

mResult[i] =mStart[i] + (mEnd[i] -mStart[i]) * value;

​ }

//设置矩阵并重绘

​ mOuterMatrix.setValues(mResult);

​ dispatchOuterMatrixChanged();

​ invalidate();

​ }

}

////////////////////////////////防止内存抖动复用对象////////////////////////////////

/**

* 对象池

* 防止频繁new对象产生内存抖动.

* 由于对象池最大长度限制,如果吞度量超过对象池容量,仍然会发生抖动.

* 此时需要增大对象池容量,但是会占用更多内存.

* @param 对象池容纳的对象类型

*/

private static abstract class ObjectsPool {

/**

​ * 对象池的最大容量

​ */

​ private int mSize;

​ /**

​ * 对象池队列

​ */

​ private QueuemQueue;

​ /**

​ * 创建一个对象池

​ *

​ * @param size 对象池最大容量

​ */

​ public ObjectsPool(int size) {

mSize = size;

​ mQueue =new LinkedList();

​ }

/**

​ * 获取一个空闲的对象

​ *

​ * 如果对象池为空,则对象池自己会new一个返回.

​ * 如果对象池内有对象,则取一个已存在的返回.

​ * take出来的对象用完要记得调用given归还.

​ * 如果不归还,让然会发生内存抖动,但不会引起泄漏.

​ * @return 可用的对象

​ *

​ * @see #given(Object)

*/

​ public T take() {

//如果池内为空就创建一个

​ if (mQueue.size() ==0) {

return newInstance();

​ }else {

//对象池里有就从顶端拿出来一个返回

​ return resetInstance(mQueue.poll());

​ }

}

/**

​ * 归还对象池内申请的对象

​ *

​ * 如果归还的对象数量超过对象池容量,那么归还的对象就会被丢弃.

​ * @param obj 归还的对象

​ *

​ * @see #take()

*/

​ public void given(T obj) {

//如果对象池还有空位子就归还对象

​ if (obj !=null &&mQueue.size()

mQueue.offer(obj);

​ }

}

/**

​ * 实例化对象

​ *

​ * @return 创建的对象

​ */

​ abstract protected T newInstance();

​ /**

​ * 重置对象

​ *

​ * 把对象数据清空到就像刚创建的一样.

​ * @param obj 需要被重置的对象

​ * @return 被重置之后的对象

​ */

​ abstract protected T resetInstance(T obj);

}

/**

* 矩阵对象池

*/

private static class MatrixPoolextends ObjectsPool {

public MatrixPool(int size) {

super(size);

​ }

@Override

​ protected MatrixnewInstance() {

return new Matrix();

​ }

@Override

​ protected MatrixresetInstance(Matrix obj) {

obj.reset();

​ return obj;

​ }

}

/**

* 矩形对象池

*/

private static class RectFPoolextends ObjectsPool {

public RectFPool(int size) {

super(size);

​ }

@Override

​ protected RectFnewInstance() {

return new RectF();

​ }

@Override

​ protected RectFresetInstance(RectF obj) {

obj.setEmpty();

​ return obj;

​ }

}

////////////////////////////////数学计算工具类////////////////////////////////

/**

* 数学计算工具类

*/

public static class MathUtils {

/**

​ * 矩阵对象池

​ */

​ private static MatrixPoolmMatrixPool =new MatrixPool(16);

​ /**

​ * 获取矩阵对象

​ */

​ public static MatrixmatrixTake() {

return mMatrixPool.take();

​ }

/**

​ * 获取某个矩阵的copy

*/

​ public static MatrixmatrixTake(Matrix matrix) {

Matrix result =mMatrixPool.take();

​ if (matrix !=null) {

result.set(matrix);

​ }

return result;

​ }

/**

​ * 归还矩阵对象

​ */

​ public static void matrixGiven(Matrix matrix) {

mMatrixPool.given(matrix);

​ }

/**

​ * 矩形对象池

​ */

​ private static RectFPoolmRectFPool =new RectFPool(16);

​ /**

​ * 获取矩形对象

​ */

​ public static RectFrectFTake() {

return mRectFPool.take();

​ }

/**

​ * 按照指定值获取矩形对象

​ */

​ public static RectFrectFTake(float left, float top, float right, float bottom) {

RectF result =mRectFPool.take();

​ result.set(left, top, right, bottom);

​ return result;

​ }

/**

​ * 获取某个矩形的副本

​ */

​ public static RectFrectFTake(RectF rectF) {

RectF result =mRectFPool.take();

​ if (rectF !=null) {

result.set(rectF);

​ }

return result;

​ }

/**

​ * 归还矩形对象

​ */

​ public static void rectFGiven(RectF rectF) {

mRectFPool.given(rectF);

​ }

/**

​ * 获取两点之间距离

​ *

​ * @param x1 点1

​ * @param y1 点1

​ * @param x2 点2

​ * @param y2 点2

​ * @return 距离

​ */

​ public static float getDistance(float x1, float y1, float x2, float y2) {

float x = x1 - x2;

​ float y = y1 - y2;

​ return (float) Math.sqrt(x * x + y * y);

​ }

/**

​ * 获取两点的中点

​ *

​ * @param x1 点1

​ * @param y1 点1

​ * @param x2 点2

​ * @param y2 点2

​ * @return float[]{x, y}

*/

​ public static float[]getCenterPoint(float x1, float y1, float x2, float y2) {

return new float[]{(x1 + x2) /2f, (y1 + y2) /2f};

​ }

/**

​ * 获取矩阵的缩放值

​ *

​ * @param matrix 要计算的矩阵

​ * @return float[]{scaleX, scaleY}

*/

​ public static float[]getMatrixScale(Matrix matrix) {

if (matrix !=null) {

float[] value =new float[9];

​ matrix.getValues(value);

​ return new float[]{value[0], value[4]};

​ }else {

return new float[2];

​ }

}

/**

​ * 计算点除以矩阵的值

​ *

* matrix.mapPoints(unknownPoint) -> point

​ * 已知point和matrix,求unknownPoint的值.

​ * @param point

​ * @param matrix

​ * @return unknownPoint

*/

​ public static float[]inverseMatrixPoint(float[] point, Matrix matrix) {

if (point !=null && matrix !=null) {

float[] dst =new float[2];

​ //计算matrix的逆矩阵

​ Matrix inverse =matrixTake();

​ matrix.invert(inverse);

​ //用逆矩阵变换point到dst,dst就是结果

​ inverse.mapPoints(dst, point);

​ //清除临时变量

​ matrixGiven(inverse);

​ return dst;

​ }else {

return new float[2];

​ }

}

/**

​ * 计算两个矩形之间的变换矩阵

​ *

* unknownMatrix.mapRect(to, from)

​ * 已知from矩形和to矩形,求unknownMatrix

​ * @param from

​ * @param to

​ * @param result unknownMatrix

*/

​ public static void calculateRectTranslateMatrix(RectF from, RectF to, Matrix result) {

if (from ==null || to ==null || result ==null) {

return;

​ }

if (from.width() ==0 || from.height() ==0) {

return;

​ }

result.reset();

​ result.postTranslate(-from.left, -from.top);

​ result.postScale(to.width() / from.width(), to.height() / from.height());

​ result.postTranslate(to.left, to.top);

​ }

/**

​ * 计算图片在某个ImageView中的显示矩形

​ *

​ * @param container ImageView的Rect

​ * @param srcWidth 图片的宽度

​ * @param srcHeight 图片的高度

​ * @param scaleType 图片在ImageView中的ScaleType

​ * @param result 图片应该在ImageView中展示的矩形

​ */

​ public static void calculateScaledRectInContainer(RectF container, float srcWidth, float srcHeight, ScaleType scaleType, RectF result) {

if (container ==null || result ==null) {

return;

​ }

if (srcWidth ==0 || srcHeight ==0) {

return;

​ }

//默认scaleType为fit center

​ if (scaleType ==null) {

scaleType = ScaleType.FIT_CENTER;

​ }

result.setEmpty();

​ if (ScaleType.FIT_XY.equals(scaleType)) {

result.set(container);

​ }else if (ScaleType.CENTER.equals(scaleType)) {

Matrix matrix =matrixTake();

​ RectF rect =rectFTake(0, 0, srcWidth, srcHeight);

​ matrix.setTranslate((container.width() - srcWidth) *0.5f, (container.height() - srcHeight) *0.5f);

​ matrix.mapRect(result, rect);

​ rectFGiven(rect);

​ matrixGiven(matrix);

​ result.left += container.left;

​ result.right += container.left;

​ result.top += container.top;

​ result.bottom += container.top;

​ }else if (ScaleType.CENTER_CROP.equals(scaleType)) {

Matrix matrix =matrixTake();

​ RectF rect =rectFTake(0, 0, srcWidth, srcHeight);

​ float scale;

​ float dx =0;

​ float dy =0;

​ if (srcWidth * container.height() > container.width() * srcHeight) {

scale = container.height() / srcHeight;

​ dx = (container.width() - srcWidth * scale) *0.5f;

​ }else {

scale = container.width() / srcWidth;

​ dy = (container.height() - srcHeight * scale) *0.5f;

​ }

matrix.setScale(scale, scale);

​ matrix.postTranslate(dx, dy);

​ matrix.mapRect(result, rect);

​ rectFGiven(rect);

​ matrixGiven(matrix);

​ result.left += container.left;

​ result.right += container.left;

​ result.top += container.top;

​ result.bottom += container.top;

​ }else if (ScaleType.CENTER_INSIDE.equals(scaleType)) {

Matrix matrix =matrixTake();

​ RectF rect =rectFTake(0, 0, srcWidth, srcHeight);

​ float scale;

​ float dx;

​ float dy;

​ if (srcWidth <= container.width() && srcHeight <= container.height()) {

scale =1f;

​ }else {

scale = Math.min(container.width() / srcWidth, container.height() / srcHeight);

​ }

dx = (container.width() - srcWidth * scale) *0.5f;

​ dy = (container.height() - srcHeight * scale) *0.5f;

​ matrix.setScale(scale, scale);

​ matrix.postTranslate(dx, dy);

​ matrix.mapRect(result, rect);

​ rectFGiven(rect);

​ matrixGiven(matrix);

​ result.left += container.left;

​ result.right += container.left;

​ result.top += container.top;

​ result.bottom += container.top;

​ }else if (ScaleType.FIT_CENTER.equals(scaleType)) {

Matrix matrix =matrixTake();

​ RectF rect =rectFTake(0, 0, srcWidth, srcHeight);

​ RectF tempSrc =rectFTake(0, 0, srcWidth, srcHeight);

​ RectF tempDst =rectFTake(0, 0, container.width(), container.height());

​ matrix.setRectToRect(tempSrc, tempDst, Matrix.ScaleToFit.CENTER);

​ matrix.mapRect(result, rect);

​ rectFGiven(tempDst);

​ rectFGiven(tempSrc);

​ rectFGiven(rect);

​ matrixGiven(matrix);

​ result.left += container.left;

​ result.right += container.left;

​ result.top += container.top;

​ result.bottom += container.top;

​ }else if (ScaleType.FIT_START.equals(scaleType)) {

Matrix matrix =matrixTake();

​ RectF rect =rectFTake(0, 0, srcWidth, srcHeight);

​ RectF tempSrc =rectFTake(0, 0, srcWidth, srcHeight);

​ RectF tempDst =rectFTake(0, 0, container.width(), container.height());

​ matrix.setRectToRect(tempSrc, tempDst, Matrix.ScaleToFit.START);

​ matrix.mapRect(result, rect);

​ rectFGiven(tempDst);

​ rectFGiven(tempSrc);

​ rectFGiven(rect);

​ matrixGiven(matrix);

​ result.left += container.left;

​ result.right += container.left;

​ result.top += container.top;

​ result.bottom += container.top;

​ }else if (ScaleType.FIT_END.equals(scaleType)) {

Matrix matrix =matrixTake();

​ RectF rect =rectFTake(0, 0, srcWidth, srcHeight);

​ RectF tempSrc =rectFTake(0, 0, srcWidth, srcHeight);

​ RectF tempDst =rectFTake(0, 0, container.width(), container.height());

​ matrix.setRectToRect(tempSrc, tempDst, Matrix.ScaleToFit.END);

​ matrix.mapRect(result, rect);

​ rectFGiven(tempDst);

​ rectFGiven(tempSrc);

​ rectFGiven(rect);

​ matrixGiven(matrix);

​ result.left += container.left;

​ result.right += container.left;

​ result.top += container.top;

​ result.bottom += container.top;

​ }else {

result.set(container);

​ }

}

}

}

转:http://my.eoe.cn/1169143/archive/21892.html

SlidingMenu简介:

SlidingMenu的是一种比较新的设置界面或配置界面效果,在主界面左滑或者右滑出现设置界面,能方便的进行各种操作.目前有大量的应用都在使用这一效果。如Evernote、Google+、Foursquare等,国内的豌豆夹,人人,360手机助手等都使用SlidingMenu的界面方案。

项目下载地址:https://github.com/jfeinstein10/SlidingMenu(直接import moudle)包内的library

SlidingMenu 常用属性介绍:

menu.setMode(SlidingMenu.LEFT);//设置左滑菜单

menu.setTouchModeAbove(SlidingMenu.TOUCHMODE_FULLSCREEN);//设置滑动的屏幕范围,该设置为全屏区域都可以滑动

menu.setShadowDrawable(R.drawable.shadow);//设置阴影图片

menu.setShadowWidthRes(R.dimen.shadow_width);//设置阴影图片的宽度

menu.setBehindOffsetRes(R.dimen.slidingmenu_offset);//SlidingMenu划出时主页面显示的剩余宽度

menu.setBehindWidth(400);//设置SlidingMenu菜单的宽度

menu.setFadeDegree(0.35f);//SlidingMenu滑动时的渐变程度

menu.attachToActivity(this, SlidingMenu.SLIDING_CONTENT);//使SlidingMenu附加在Activity上

menu.setMenu(R.layout.menu_layout);//设置menu的布局文件

menu.toggle();//动态判断自动关闭或开启SlidingMenu

menu.showMenu();//显示SlidingMenu

menu.showContent();//显示内容

menu.setOnOpenListener(onOpenListener);//监听slidingmenu打开

关于关闭menu有两个监听,简单的来说,对于menu close事件,一个是when,一个是after

menu.OnClosedListener(OnClosedListener);//监听slidingmenu关闭时事件

menu.OnClosedListener(OnClosedListener);//监听slidingmenu关闭后事件

左右都可以划出SlidingMenu菜单只需要设置

menu.setMode(SlidingMenu.LEFT_RIGHT);属性,然后设置右侧菜单的布局文件

menu.setSecondaryShadowDrawable(R.drawable.shadowright);//右侧菜单的阴影图片

设置SlidingMenu属性

sm = getSlidingMenu();

//如果只显示左侧菜单就是用LEFT,右侧就RIGHT,左右都支持就LEFT_RIGHT

sm.setMode(SlidingMenu.LEFT_RIGHT);//设置菜单滑动模式,菜单是出现在左侧还是右侧,还是左右两侧都有

sm.setShadowDrawable(R.drawable.shadow);//设置阴影的图片资源

sm.setShadowWidthRes(R.dimen.shadow_width);//设置阴影图片的宽度

//sm.setBehindWidth(200);//设置菜单的宽

sm.setBehindOffsetRes(R.dimen.slidingmenu_offset);//SlidingMenu划出时主页面显示的剩余宽度

sm.setTouchModeAbove(SlidingMenu.TOUCHMODE_FULLSCREEN);//设置滑动的区域

支持右侧划出菜单:

//SlidingMenu可以同时支持划出左右两侧的菜单,互不冲突,而且动画优美,体验良好。

sm.setSecondaryMenu(R.layout.menu_frame2);//设置右侧菜单

sm.setSecondaryShadowDrawable(R.drawable.shadowright);//设置右侧菜单阴影的图片资源

//右侧SlidingMenu的Fragment

getSupportFragmentManager().beginTransaction().replace(R.id.menu_frame2, new SampleListFragment()).commit();

slidingMenu = getSlidingMenu();

//设置是左滑还是右滑,还是左右都可以滑

slidingMenu.setMode(SlidingMenu.LEFT_RIGHT);

//设置阴影宽度

slidingMenu.setShadowWidth(getWindowManager().getDefaultDisplay().getWidth() / 40);

//设置左菜单阴影图片

slidingMenu.setShadowDrawable(R.drawable.shadow);

//设置右菜单阴影图片

slidingMenu.setSecondaryShadowDrawable(R.drawable.right_shadow);

//设置菜单占屏幕的比例

slidingMenu.setBehindOffset(getWindowManager().getDefaultDisplay().getWidth() / 5);

//设置滑动时菜单的是否淡入淡出

slidingMenu.setFadeEnabled(true);

//设置淡入淡出的比例

slidingMenu.setFadeDegree(0.4f);

//设置滑动时拖拽效果

slidingMenu.setBehindScrollScale(0);

//设置要使菜单滑动,触碰屏幕的范围

slidingMenu.setTouchModeAbove(SlidingMenu.TOUCHMODE_FULLSCREEN);

####找到本地platform-tools文件路径:
android Studio-Tools-SDKManager
SDKManager
platform-tools
将platform-tools路径配置到环境变量的path中
环境变量
cmd 输入adb运行显示版本信息等既成功
adb配置成功

  • adb devices (列出当前连接的设备列表)
  • adb -s 设备 shell (指定手机设备)
  • adb shell (进入手机管理)
  • adb install 文件 (安装文件)
  • adb push 电脑端文件 手机端文件路径(从电脑端发送文件到手机端)
  • adb pull 手机端文件路径 电脑端文件路径(从手机端复制文件到电脑端)
  • adb chmod 777 文件/文件夹路径(指定要给权限的文件或文件夹)
  • adb cd 路径(进入目录)
  • adb cat (查看文件内容)
  • adb id(获取当前用户信息)
  • adb ps(查看当前系统所有进程)
  • adb kill 进程id(杀进程)
  • adb ls (列出当前文件夹下的文件)
  • adb mkdir(创建文件夹)
  • adb touch(c创建一个空文件)
  • adb rm(移除文件)
  • adb rm -r (移除文件夹)
  • adb cp(复制文件)
  • adb mv 要移动的文件,重命名文件(移动文件)