├── .gitignore
├── .idea
├── .name
├── compiler.xml
├── copyright
│ └── profiles_settings.xml
├── encodings.xml
├── gradle.xml
├── misc.xml
├── modules.xml
└── vcs.xml
├── PhotoView.iml
├── README.md
├── app
├── .gitignore
├── app.iml
├── build.gradle
├── libs
│ └── universal-image-loader-1.9.4.jar
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── example
│ │ └── bm
│ │ └── photoview
│ │ └── ApplicationTest.java
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── example
│ │ └── bm
│ │ └── photoview
│ │ ├── ImageViewActivity.java
│ │ ├── ImgActivity.java
│ │ ├── ImgClick.java
│ │ ├── MainActivity.java
│ │ ├── PhotoBrowse.java
│ │ └── ViewPagerActivity.java
│ └── res
│ ├── layout
│ ├── activity_image_view.xml
│ ├── activity_img.xml
│ ├── activity_img_click.xml
│ ├── activity_main.xml
│ ├── activity_photo_browse.xml
│ └── activity_view_pager.xml
│ ├── mipmap-hdpi
│ ├── aaa.png
│ ├── bbb.jpg
│ ├── ccc.png
│ ├── ddd.png
│ ├── ic_launcher.png
│ └── image003.png
│ ├── mipmap-mdpi
│ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ ├── values-w820dp
│ └── dimens.xml
│ └── values
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
├── build.gradle
├── demo.apk
├── demo1.gif
├── demo2.gif
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── library
├── .gitignore
├── build.gradle
├── library.iml
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── bm
│ │ └── library
│ │ └── ApplicationTest.java
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── bm
│ └── library
│ ├── Info.java
│ ├── PhotoView.java
│ └── RotateGestureDetector.java
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | /local.properties
3 | /.idea/workspace.xml
4 | /.idea/libraries
5 | .DS_Store
6 | /build
7 | /captures
--------------------------------------------------------------------------------
/.idea/.name:
--------------------------------------------------------------------------------
1 | PhotoView
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
24 |
25 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/PhotoView.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PhotoView 图片浏览缩放控件
2 |
3 | 一个流畅的photoview
4 |
5 | # 效果图
6 |  
7 |
8 | # 注意
9 | 由于facebook的Fresco图片加载组件所加载出来的drawable图片并非真实的drawable,无法直接获取图片真实宽高,也无法直接响应ImageMatrix的变换,
10 | 且根据Fresco文档的介绍,在后续的版本中,DraweeView会直接继承自View,所有暂不考虑支持Fresco。
11 | 对于其他第三方图片加载库如Glide,ImageLoader,xUtils都是支持的
12 |
13 | # 使用
14 | 1.Gradle添加依赖 (推荐)
15 | ```gradle
16 | dependencies {
17 | compile 'com.bm.photoview:library:1.4.1'
18 | }
19 | ```
20 | (或者也可以将项目下载下来,将Info.java和PhotoView.java两个文件拷贝到你的项目中,不推荐)
21 |
22 | 2.xml添加
23 | ```xml
24 |
30 | ```
31 |
32 | 3.java代码
33 | ```java
34 | PhotoView photoView = (PhotoView) findViewById(R.id.img);
35 | // 启用图片缩放功能
36 | photoView.enable();
37 | // 禁用图片缩放功能 (默认为禁用,会跟普通的ImageView一样,缩放功能需手动调用enable()启用)
38 | photoView.disenable();
39 | // 获取图片信息
40 | Info info = photoView.getInfo();
41 | // 从普通的ImageView中获取Info
42 | Info info = PhotoView.getImageViewInfo(ImageView);
43 | // 从一张图片信息变化到现在的图片,用于图片点击后放大浏览,具体使用可以参照demo的使用
44 | photoView.animaFrom(info);
45 | // 从现在的图片变化到所给定的图片信息,用于图片放大后点击缩小到原来的位置,具体使用可以参照demo的使用
46 | photoView.animaTo(info,new Runnable() {
47 | @Override
48 | public void run() {
49 | //动画完成监听
50 | }
51 | });
52 | // 获取/设置 动画持续时间
53 | photoView.setAnimaDuring(int during);
54 | int d = photoView.getAnimaDuring();
55 | // 获取/设置 最大缩放倍数
56 | photoView.setMaxScale(float maxScale);
57 | float maxScale = photoView.getMaxScale();
58 | // 设置动画的插入器
59 | photoView.setInterpolator(Interpolator interpolator);
60 | ```
61 |
62 | # 关于
63 |
64 | 若遇到使用上的问题,请先翻看Issues,大部分问题是已经有人提出过的。(如[#9](https://github.com/bm-x/PhotoView/issues/9),[#5](https://github.com/bm-x/PhotoView/issues/5))
65 | 若没找到相关的问题,可以先在Issues中提出,这样以便其他人遇到同样问题时可快速找到答案。
66 | 若长时间未回复,可邮件给我bmme@vip.qq.com
67 |
68 | # 版本
69 |
70 | v1.4.0
71 | * 增加对普通ImageView的支持,可通过PhotoView的静态方法getImageViewInfo(ImageView)从一个普通的ImageView中获取Info,参照ImageViewActivity
72 | * 添加长按事件的监听,setOnLongClickListener()
73 | * 提高图片缩放到屏幕边缘的情况下滑动的流畅性
74 | * 新增get/setAnimaDuring() get/setMaxScale 获取设置动画的持续时间和图片最大缩放倍数
75 | * 通过setInterpolator可设置动画插入器
76 |
77 | v1.3.6
78 | * 增加图片的旋转功能
79 | * 版本号命名改变
80 |
81 | v2.0.7
82 | * 宽高属性可以设置为wrap_content,添加对adjustViewBounds属性的支持
83 | * 修复某些情况下会闪动
84 | * 增加对ScaleType.FIT_START,FIT_END对animaFrom的支持
85 |
86 | v2.0.0
87 | * 添加animaTo,animaFrom方法,支持图片点击放大缩小浏览功能
88 | * 添加enable()和disenable() 打开和关闭触摸缩放方法,默认打开 (当普通ImageView使用的时候建议关闭触摸缩放功能)
89 | * 支持所有ScaleType属性
90 |
91 | v1.0
92 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/app.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | generateDebugSources
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 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 23
5 | buildToolsVersion "23.0.3"
6 |
7 | defaultConfig {
8 | applicationId "com.example.bm.photoview"
9 | minSdkVersion 10
10 | targetSdkVersion 23
11 | versionCode 2
12 | versionName "2.0.2"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(include: ['*.jar'], dir: 'libs')
24 | compile 'com.android.support:appcompat-v7:23.1.0'
25 | compile 'com.github.bumptech.glide:glide:3.6.1'
26 | compile project(':library')
27 | }
28 |
--------------------------------------------------------------------------------
/app/libs/universal-image-loader-1.9.4.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bm-x/PhotoView/5e20691bb7c37e4ca3f678574a16a53e5006aaa6/app/libs/universal-image-loader-1.9.4.jar
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in C:\sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/example/bm/photoview/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.example.bm.photoview;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
13 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
25 |
28 |
31 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/bm/photoview/ImageViewActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.bm.photoview;
2 |
3 | import android.app.Activity;
4 | import android.support.v4.view.animation.FastOutSlowInInterpolator;
5 | import android.support.v7.app.AppCompatActivity;
6 | import android.os.Bundle;
7 | import android.view.View;
8 | import android.view.Window;
9 | import android.view.animation.LinearInterpolator;
10 | import android.view.animation.OvershootInterpolator;
11 | import android.widget.ImageView;
12 | import android.widget.Scroller;
13 |
14 | import com.bm.library.Info;
15 | import com.bm.library.PhotoView;
16 |
17 | public class ImageViewActivity extends Activity {
18 |
19 | ImageView img;
20 | PhotoView photoView;
21 |
22 | Info mInfo;
23 |
24 | @Override
25 | protected void onCreate(Bundle savedInstanceState) {
26 | super.onCreate(savedInstanceState);
27 | setContentView(R.layout.activity_image_view);
28 |
29 | img = (ImageView) findViewById(R.id.img);
30 | photoView = (PhotoView) findViewById(R.id.photoview);
31 | photoView.enable();
32 |
33 | img.setOnClickListener(new View.OnClickListener() {
34 | @Override
35 | public void onClick(View v) {
36 | mInfo = PhotoView.getImageViewInfo(img);
37 | img.setVisibility(View.GONE);
38 | photoView.setVisibility(View.VISIBLE);
39 | photoView.animaFrom(mInfo);
40 | }
41 | });
42 |
43 | photoView.setOnClickListener(new View.OnClickListener() {
44 | @Override
45 | public void onClick(View v) {
46 | photoView.animaTo(mInfo, new Runnable() {
47 | @Override
48 | public void run() {
49 | photoView.setVisibility(View.GONE);
50 | img.setVisibility(View.VISIBLE);
51 | }
52 | });
53 | }
54 | });
55 | }
56 |
57 |
58 | @Override
59 | public void onBackPressed() {
60 | if (photoView.getVisibility() == View.VISIBLE) {
61 | photoView.animaTo(mInfo, new Runnable() {
62 | @Override
63 | public void run() {
64 | photoView.setVisibility(View.GONE);
65 | img.setVisibility(View.VISIBLE);
66 | }
67 | });
68 | } else {
69 | super.onBackPressed();
70 | }
71 | }
72 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/bm/photoview/ImgActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.bm.photoview;
2 |
3 | import android.app.Activity;
4 | import android.os.Bundle;
5 | import android.view.View;
6 | import android.widget.Toast;
7 |
8 | import com.bm.library.PhotoView;
9 |
10 | /**
11 | * Created by liuheng on 2015/6/21.
12 | */
13 | public class ImgActivity extends Activity {
14 |
15 | String url = "https://ss0.baidu.com/94o3dSag_xI4khGko9WTAnF6hhy/super/whfpf%3D425%2C260%2C50/sign=30f49b810ff79052ef4a147e6acee3f8/5bafa40f4bfbfbedbb34deed7ef0f736afc31f36.jpg";
16 | String gif = "http://imgsrc.baidu.com/baike/pic/item/7af40ad162d9f2d339d2a789abec8a136227cc91.jpg";
17 |
18 | PhotoView mPhotoView;
19 |
20 | @Override
21 | protected void onCreate(Bundle savedInstanceState) {
22 | super.onCreate(savedInstanceState);
23 | setContentView(R.layout.activity_img);
24 |
25 | mPhotoView = (PhotoView) findViewById(R.id.img1);
26 | mPhotoView.enable();
27 |
28 | mPhotoView.setOnLongClickListener(new View.OnLongClickListener() {
29 | @Override
30 | public boolean onLongClick(View v) {
31 | Toast.makeText(ImgActivity.this, "长按了", Toast.LENGTH_SHORT).show();
32 | return false;
33 | }
34 | });
35 |
36 | // 使用ImageLoader
37 | // ImaggeLoaderConfiguration configuration = ImageLoaderConfiguration
38 | // .createDefault(this);
39 | // ImageLoader.getInstance().init(configuration);
40 | // ImageLoader.etInstance().displayImage(url, (ImageView) findViewById(R.id.img1));
41 |
42 | // 使用Glide加载的gif图片同样支持缩放功能
43 | // Glide.with(this)
44 | // .load(gif)
45 | // .crossFade()
46 | // .placeholder(R.mipmap.bbb)
47 | // .into(((PhotoView) findViewById(R.id.img1)));
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/bm/photoview/ImgClick.java:
--------------------------------------------------------------------------------
1 | package com.example.bm.photoview;
2 |
3 | import android.app.Activity;
4 | import android.os.Bundle;
5 | import android.view.View;
6 | import android.view.Window;
7 | import android.view.WindowManager;
8 | import android.widget.ImageView;
9 | import android.widget.RadioGroup;
10 |
11 | import com.bm.library.Info;
12 | import com.bm.library.PhotoView;
13 |
14 | /**
15 | * Created by liuheng on 2015/6/21.
16 | */
17 | public class ImgClick extends Activity implements RadioGroup.OnCheckedChangeListener {
18 |
19 | Info mRectF;
20 |
21 | PhotoView mImg1;
22 | PhotoView mImg2;
23 |
24 | @Override
25 | protected void onCreate(Bundle savedInstanceState) {
26 | super.onCreate(savedInstanceState);
27 | // getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
28 | setContentView(R.layout.activity_img_click);
29 |
30 | ((RadioGroup) findViewById(R.id.group)).setOnCheckedChangeListener(this);
31 |
32 | mImg1 = (PhotoView) findViewById(R.id.img1);
33 | mImg2 = (PhotoView) findViewById(R.id.img2);
34 |
35 | //设置不可以双指缩放移动放大等操作,跟普通的image一模一样,默认情况下就是disenable()状态
36 | mImg1.disenable();
37 | mImg1.setOnClickListener(new View.OnClickListener() {
38 | @Override
39 | public void onClick(View v) {
40 | mImg1.setVisibility(View.GONE);
41 | mImg2.setVisibility(View.VISIBLE);
42 |
43 | //获取img1的信息
44 | mRectF = mImg1.getInfo();
45 | //让img2从img1的位置变换到他本身的位置
46 | mImg2.animaFrom(mRectF);
47 | }
48 | });
49 |
50 | // 需要启动缩放需要手动开启
51 | mImg2.enable();
52 | mImg2.setOnClickListener(new View.OnClickListener() {
53 | @Override
54 | public void onClick(View v) {
55 | // 让img2从自身位置变换到原来img1图片的位置大小
56 | mImg2.animaTo(mRectF, new Runnable() {
57 | @Override
58 | public void run() {
59 | mImg2.setVisibility(View.GONE);
60 | mImg1.setVisibility(View.VISIBLE);
61 | }
62 | });
63 | }
64 | });
65 | }
66 |
67 | @Override
68 | public void onBackPressed() {
69 | if (mImg2.getVisibility() == View.VISIBLE) {
70 | mImg2.animaTo(mRectF, new Runnable() {
71 | @Override
72 | public void run() {
73 | mImg2.setVisibility(View.GONE);
74 | mImg1.setVisibility(View.VISIBLE);
75 | }
76 | });
77 | } else {
78 | super.onBackPressed();
79 | }
80 | }
81 |
82 | @Override
83 | public void onCheckedChanged(RadioGroup group, int checkedId) {
84 |
85 | switch (checkedId) {
86 | case R.id.center:
87 | mImg1.setScaleType(ImageView.ScaleType.CENTER);
88 | break;
89 | case R.id.center_crop:
90 | mImg1.setScaleType(ImageView.ScaleType.CENTER_CROP);
91 | break;
92 | case R.id.center_inside:
93 | mImg1.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
94 | break;
95 | case R.id.fit_center:
96 | mImg1.setScaleType(ImageView.ScaleType.FIT_CENTER);
97 | break;
98 |
99 | // 建议用了fit_Xy,fit_end,fit_start就不要使用缩放或者animaFrom或animaTo
100 | case R.id.fit_end:
101 | mImg1.setScaleType(ImageView.ScaleType.FIT_END);
102 | break;
103 | case R.id.fit_start:
104 | mImg1.setScaleType(ImageView.ScaleType.FIT_START);
105 | break;
106 | case R.id.fit_xy:
107 | mImg1.setScaleType(ImageView.ScaleType.FIT_XY);
108 | break;
109 | }
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/bm/photoview/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.bm.photoview;
2 |
3 | import android.app.Activity;
4 | import android.content.Intent;
5 | import android.graphics.Matrix;
6 | import android.graphics.RectF;
7 | import android.os.Bundle;
8 | import android.util.Log;
9 | import android.view.View;
10 |
11 | /**
12 | * Created by liuheng on 2015/6/21.
13 | */
14 | public class MainActivity extends Activity {
15 |
16 | @Override
17 | protected void onCreate(Bundle savedInstanceState) {
18 | super.onCreate(savedInstanceState);
19 | setContentView(R.layout.activity_main);
20 | }
21 |
22 | public void img(View view) {
23 | startActivity(new Intent(this, ImgActivity.class));
24 | }
25 |
26 | public void viewpager(View view) {
27 | startActivity(new Intent(this, ViewPagerActivity.class));
28 | }
29 |
30 | public void imgclick(View view) {
31 | startActivity(new Intent(this, ImgClick.class));
32 | }
33 |
34 | public void photobrowse(View view) {
35 | startActivity(new Intent(this, PhotoBrowse.class));
36 | }
37 |
38 | public void imageview(View view) {
39 | startActivity(new Intent(this, ImageViewActivity.class));
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/bm/photoview/PhotoBrowse.java:
--------------------------------------------------------------------------------
1 | package com.example.bm.photoview;
2 |
3 | import android.app.Activity;
4 | import android.os.Bundle;
5 | import android.view.View;
6 | import android.view.ViewGroup;
7 | import android.view.Window;
8 | import android.view.animation.AlphaAnimation;
9 | import android.view.animation.Animation;
10 | import android.widget.AbsListView;
11 | import android.widget.AdapterView;
12 | import android.widget.BaseAdapter;
13 | import android.widget.GridView;
14 | import android.widget.ImageView;
15 |
16 | import com.bm.library.Info;
17 | import com.bm.library.PhotoView;
18 |
19 | /**
20 | * Created by liuheng on 2015/8/19.
21 | */
22 | public class PhotoBrowse extends Activity {
23 |
24 | int[] imgs = new int[]{R.mipmap.aaa, R.mipmap.bbb, R.mipmap.ccc, R.mipmap.ddd, R.mipmap.ic_launcher, R.mipmap.image003};
25 |
26 | GridView gv;
27 |
28 | View mParent;
29 | View mBg;
30 | PhotoView mPhotoView;
31 | Info mInfo;
32 |
33 | AlphaAnimation in = new AlphaAnimation(0, 1);
34 | AlphaAnimation out = new AlphaAnimation(1, 0);
35 |
36 | @Override
37 | protected void onCreate(Bundle savedInstanceState) {
38 | super.onCreate(savedInstanceState);
39 | requestWindowFeature(Window.FEATURE_NO_TITLE);
40 | setContentView(R.layout.activity_photo_browse);
41 |
42 | in.setDuration(300);
43 | out.setDuration(300);
44 | out.setAnimationListener(new Animation.AnimationListener() {
45 | @Override
46 | public void onAnimationStart(Animation animation) {
47 |
48 | }
49 |
50 | @Override
51 | public void onAnimationEnd(Animation animation) {
52 | mBg.setVisibility(View.INVISIBLE);
53 | }
54 |
55 | @Override
56 | public void onAnimationRepeat(Animation animation) {
57 |
58 | }
59 | });
60 |
61 | mParent = findViewById(R.id.parent);
62 | mBg = findViewById(R.id.bg);
63 | mPhotoView = (PhotoView) findViewById(R.id.img);
64 | mPhotoView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
65 |
66 | gv = (GridView) findViewById(R.id.gv);
67 | gv.setAdapter(new BaseAdapter() {
68 | @Override
69 | public int getCount() {
70 | return imgs.length;
71 | }
72 |
73 | @Override
74 | public Object getItem(int position) {
75 | return null;
76 | }
77 |
78 | @Override
79 | public long getItemId(int position) {
80 | return 0;
81 | }
82 |
83 | @Override
84 | public View getView(int position, View convertView, ViewGroup parent) {
85 | PhotoView p = new PhotoView(PhotoBrowse.this);
86 | p.setLayoutParams(new AbsListView.LayoutParams((int) (getResources().getDisplayMetrics().density * 100), (int) (getResources().getDisplayMetrics().density * 100)));
87 | p.setScaleType(ImageView.ScaleType.CENTER_CROP);
88 | p.setImageResource(imgs[position]);
89 | // 把PhotoView当普通的控件把触摸功能关掉
90 | p.disenable();
91 | return p;
92 | }
93 | });
94 |
95 | gv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
96 | @Override
97 | public void onItemClick(AdapterView> parent, View view, int position, long id) {
98 | PhotoView p = (PhotoView) view;
99 | mInfo = p.getInfo();
100 |
101 | mPhotoView.setImageResource(imgs[position]);
102 | mBg.startAnimation(in);
103 | mBg.setVisibility(View.VISIBLE);
104 | mParent.setVisibility(View.VISIBLE);;
105 | mPhotoView.animaFrom(mInfo);
106 | }
107 | });
108 |
109 | mPhotoView.enable();
110 | mPhotoView.setOnClickListener(new View.OnClickListener() {
111 | @Override
112 | public void onClick(View v) {
113 | mBg.startAnimation(out);
114 | mPhotoView.animaTo(mInfo, new Runnable() {
115 | @Override
116 | public void run() {
117 | mParent.setVisibility(View.GONE);
118 | }
119 | });
120 | }
121 | });
122 | }
123 |
124 | @Override
125 | public void onBackPressed() {
126 | if (mParent.getVisibility() == View.VISIBLE) {
127 | mBg.startAnimation(out);
128 | mPhotoView.animaTo(mInfo, new Runnable() {
129 | @Override
130 | public void run() {
131 | mParent.setVisibility(View.GONE);
132 | }
133 | });
134 | } else {
135 | super.onBackPressed();
136 | }
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/bm/photoview/ViewPagerActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.bm.photoview;
2 |
3 | import android.app.Activity;
4 | import android.support.v4.view.PagerAdapter;
5 | import android.support.v4.view.ViewPager;
6 | import android.os.Bundle;
7 | import android.view.View;
8 | import android.view.ViewGroup;
9 | import android.widget.ImageView;
10 |
11 | import com.bm.library.PhotoView;
12 |
13 | /**
14 | * Created by liuheng on 2015/8/19.
15 | */
16 | public class ViewPagerActivity extends Activity {
17 |
18 | private ViewPager mPager;
19 |
20 | private int[] imgsId = new int[]{R.mipmap.aaa, R.mipmap.bbb, R.mipmap.ccc, R.mipmap.ddd, R.mipmap.ic_launcher, R.mipmap.image003};
21 |
22 | @Override
23 | protected void onCreate(Bundle savedInstanceState) {
24 | super.onCreate(savedInstanceState);
25 | setContentView(R.layout.activity_view_pager);
26 |
27 | mPager = (ViewPager) findViewById(R.id.pager);
28 | mPager.setPageMargin((int) (getResources().getDisplayMetrics().density * 15));
29 | mPager.setAdapter(new PagerAdapter() {
30 | @Override
31 | public int getCount() {
32 | return imgsId.length;
33 | }
34 |
35 | @Override
36 | public boolean isViewFromObject(View view, Object object) {
37 | return view == object;
38 | }
39 |
40 | @Override
41 | public Object instantiateItem(ViewGroup container, int position) {
42 | PhotoView view = new PhotoView(ViewPagerActivity.this);
43 | view.enable();
44 | view.setScaleType(ImageView.ScaleType.FIT_CENTER);
45 | view.setImageResource(imgsId[position]);
46 | container.addView(view);
47 | return view;
48 | }
49 |
50 | @Override
51 | public void destroyItem(ViewGroup container, int position, Object object) {
52 | container.removeView((View) object);
53 | }
54 | });
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_image_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
15 |
16 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_img.xml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_img_click.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
16 |
17 |
24 |
25 |
30 |
31 |
37 |
38 |
43 |
44 |
49 |
50 |
55 |
56 |
61 |
62 |
67 |
68 |
69 |
70 |
78 |
79 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
11 |
12 |
18 |
19 |
25 |
26 |
32 |
33 |
39 |
40 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_photo_browse.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
15 |
16 |
21 |
22 |
27 |
28 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_view_pager.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/aaa.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bm-x/PhotoView/5e20691bb7c37e4ca3f678574a16a53e5006aaa6/app/src/main/res/mipmap-hdpi/aaa.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/bbb.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bm-x/PhotoView/5e20691bb7c37e4ca3f678574a16a53e5006aaa6/app/src/main/res/mipmap-hdpi/bbb.jpg
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ccc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bm-x/PhotoView/5e20691bb7c37e4ca3f678574a16a53e5006aaa6/app/src/main/res/mipmap-hdpi/ccc.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ddd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bm-x/PhotoView/5e20691bb7c37e4ca3f678574a16a53e5006aaa6/app/src/main/res/mipmap-hdpi/ddd.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bm-x/PhotoView/5e20691bb7c37e4ca3f678574a16a53e5006aaa6/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/image003.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bm-x/PhotoView/5e20691bb7c37e4ca3f678574a16a53e5006aaa6/app/src/main/res/mipmap-hdpi/image003.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bm-x/PhotoView/5e20691bb7c37e4ca3f678574a16a53e5006aaa6/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bm-x/PhotoView/5e20691bb7c37e4ca3f678574a16a53e5006aaa6/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bm-x/PhotoView/5e20691bb7c37e4ca3f678574a16a53e5006aaa6/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | PhotoView
3 |
4 | Hello world!
5 | Settings
6 | ImgActivity
7 | ViewPagerActivity
8 | ImgClick
9 | PhotoBrowse
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:2.1.2'
9 | // classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3'
10 | // classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.0'
11 | // NOTE: Do not place your application dependencies here; they belong
12 | // in the individual module build.gradle files
13 | }
14 | }
15 |
16 | allprojects {
17 | repositories {
18 | jcenter()
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/demo.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bm-x/PhotoView/5e20691bb7c37e4ca3f678574a16a53e5006aaa6/demo.apk
--------------------------------------------------------------------------------
/demo1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bm-x/PhotoView/5e20691bb7c37e4ca3f678574a16a53e5006aaa6/demo1.gif
--------------------------------------------------------------------------------
/demo2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bm-x/PhotoView/5e20691bb7c37e4ca3f678574a16a53e5006aaa6/demo2.gif
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bm-x/PhotoView/5e20691bb7c37e4ca3f678574a16a53e5006aaa6/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Apr 10 15:27:10 PDT 2013
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/library/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/library/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | //apply plugin: 'com.github.dcendents.android-maven'
3 | //apply plugin: 'com.jfrog.bintray'
4 |
5 | version "1.4.1"
6 |
7 | android {
8 | compileSdkVersion 23
9 | buildToolsVersion "23.0.3"
10 | resourcePrefix "photoview"
11 |
12 | defaultConfig {
13 | minSdkVersion 10
14 | targetSdkVersion 23
15 | versionCode 5
16 | versionName version
17 | }
18 | buildTypes {
19 | release {
20 | minifyEnabled false
21 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
22 | }
23 | }
24 | }
25 |
26 | dependencies {
27 | compile fileTree(include: ['*.jar'], dir: 'libs')
28 | }
29 |
30 | //def siteUrl = 'https://github.com/bm-x/PhotoView'
31 | //def gitUrl = 'https://github.com/bm-x/PhotoView.git'
32 | //group = "com.bm.photoview"
33 | //install {
34 | // repositories.mavenInstaller {
35 | // //This generates POM.xml with proper parameters
36 | // pom {
37 | // project {
38 | // packaging 'aar'
39 | // // Add your description here
40 | // name 'PhotoView'
41 | // url siteUrl
42 | // // Set your license
43 | // licenses {
44 | // license {
45 | // name 'The Apache Software License, Version 2.0'
46 | // url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
47 | // }
48 | // }
49 | // developers {
50 | // developer {
51 | // id 'photoview'
52 | // name 'bmme'
53 | // email 'bmme@vip.qq.com'
54 | // }
55 | // }
56 | // scm {
57 | // connection gitUrl
58 | // developerConnection gitUrl
59 | // url siteUrl
60 | // }
61 | // }
62 | // }
63 | // }
64 | //}
65 | //
66 | //task sourcesJar(type: Jar) {
67 | // from android.sourceSets.main.java.srcDirs
68 | // classifier = 'sources'
69 | //}
70 | //task javadoc(type: Javadoc) {
71 | // options.encoding = "UTF-8"
72 | // source = android.sourceSets.main.java.srcDirs
73 | // classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
74 | //}
75 | //task javadocJar(type: Jar, dependsOn: javadoc) {
76 | // classifier = 'javadoc'
77 | // from javadoc.destinationDir
78 | //}
79 | //artifacts {
80 | // archives javadocJar
81 | // archives sourcesJar
82 | //}
83 | //Properties properties = new Properties()
84 | //properties.load(project.rootProject.file('local.properties').newDataInputStream())
85 | //bintray {
86 | // user = properties.getProperty("bintray.user")
87 | // key = properties.getProperty("bintray.apikey")
88 | // configurations = ['archives']
89 | // pkg {
90 | // repo = "maven"
91 | // name = "PhotoView"
92 | // websiteUrl = siteUrl
93 | // vcsUrl = gitUrl
94 | // licenses = ["Apache-2.0"]
95 | // publish = true
96 | // }
97 | //}
98 |
99 | // gradlew bintrayUpload
--------------------------------------------------------------------------------
/library/library.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | generateDebugSources
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 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
--------------------------------------------------------------------------------
/library/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in D:\dev\sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/library/src/androidTest/java/com/bm/library/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.bm.library;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/library/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/library/src/main/java/com/bm/library/Info.java:
--------------------------------------------------------------------------------
1 | package com.bm.library;
2 |
3 | import android.graphics.PointF;
4 | import android.graphics.RectF;
5 | import android.widget.ImageView;
6 |
7 | /**
8 | * Created by liuheng on 2015/8/19.
9 | */
10 | public class Info {
11 |
12 | // 内部图片在整个手机界面的位置
13 | RectF mRect = new RectF();
14 |
15 | // 控件在窗口的位置
16 | RectF mImgRect = new RectF();
17 |
18 | RectF mWidgetRect = new RectF();
19 |
20 | RectF mBaseRect = new RectF();
21 |
22 | PointF mScreenCenter = new PointF();
23 |
24 | float mScale;
25 |
26 | float mDegrees;
27 |
28 | ImageView.ScaleType mScaleType;
29 |
30 | public Info(RectF rect, RectF img, RectF widget, RectF base, PointF screenCenter, float scale, float degrees, ImageView.ScaleType scaleType) {
31 | mRect.set(rect);
32 | mImgRect.set(img);
33 | mWidgetRect.set(widget);
34 | mScale = scale;
35 | mScaleType = scaleType;
36 | mDegrees = degrees;
37 | mBaseRect.set(base);
38 | mScreenCenter.set(screenCenter);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/library/src/main/java/com/bm/library/PhotoView.java:
--------------------------------------------------------------------------------
1 | package com.bm.library;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.graphics.Matrix;
6 | import android.graphics.PointF;
7 | import android.graphics.RectF;
8 | import android.graphics.drawable.Drawable;
9 | import android.util.AttributeSet;
10 | import android.view.GestureDetector;
11 | import android.view.MotionEvent;
12 | import android.view.ScaleGestureDetector;
13 | import android.view.View;
14 | import android.view.ViewGroup;
15 | import android.view.ViewParent;
16 | import android.view.animation.DecelerateInterpolator;
17 | import android.view.animation.Interpolator;
18 | import android.widget.ImageView;
19 | import android.widget.OverScroller;
20 | import android.widget.Scroller;
21 |
22 | /**
23 | * Created by liuheng on 2015/6/21.
24 | *
25 | * 如有任何意见和建议可邮件 bmme@vip.qq.com
26 | */
27 | public class PhotoView extends ImageView {
28 |
29 | private final static int MIN_ROTATE = 35;
30 | private final static int ANIMA_DURING = 340;
31 | private final static float MAX_SCALE = 2.5f;
32 |
33 | private int mMinRotate;
34 | private int mAnimaDuring;
35 | private float mMaxScale;
36 |
37 | private int MAX_OVER_SCROLL = 0;
38 | private int MAX_FLING_OVER_SCROLL = 0;
39 | private int MAX_OVER_RESISTANCE = 0;
40 | private int MAX_ANIM_FROM_WAITE = 500;
41 |
42 | private Matrix mBaseMatrix = new Matrix();
43 | private Matrix mAnimaMatrix = new Matrix();
44 | private Matrix mSynthesisMatrix = new Matrix();
45 | private Matrix mTmpMatrix = new Matrix();
46 |
47 | private RotateGestureDetector mRotateDetector;
48 | private GestureDetector mDetector;
49 | private ScaleGestureDetector mScaleDetector;
50 | private OnClickListener mClickListener;
51 |
52 | private ScaleType mScaleType;
53 |
54 | private boolean hasMultiTouch;
55 | private boolean hasDrawable;
56 | private boolean isKnowSize;
57 | private boolean hasOverTranslate;
58 | private boolean isEnable = false;
59 | private boolean isRotateEnable = false;
60 | private boolean isInit;
61 | private boolean mAdjustViewBounds;
62 | // 当前是否处于放大状态
63 | private boolean isZoonUp;
64 | private boolean canRotate;
65 |
66 | private boolean imgLargeWidth;
67 | private boolean imgLargeHeight;
68 |
69 | private float mRotateFlag;
70 | private float mDegrees;
71 | private float mScale = 1.0f;
72 | private int mTranslateX;
73 | private int mTranslateY;
74 |
75 | private float mHalfBaseRectWidth;
76 | private float mHalfBaseRectHeight;
77 |
78 | private RectF mWidgetRect = new RectF();
79 | private RectF mBaseRect = new RectF();
80 | private RectF mImgRect = new RectF();
81 | private RectF mTmpRect = new RectF();
82 | private RectF mCommonRect = new RectF();
83 |
84 | private PointF mScreenCenter = new PointF();
85 | private PointF mScaleCenter = new PointF();
86 | private PointF mRotateCenter = new PointF();
87 |
88 | private Transform mTranslate = new Transform();
89 |
90 | private RectF mClip;
91 | private Info mFromInfo;
92 | private long mInfoTime;
93 | private Runnable mCompleteCallBack;
94 |
95 | private OnLongClickListener mLongClick;
96 |
97 | public PhotoView(Context context) {
98 | super(context);
99 | init();
100 | }
101 |
102 | public PhotoView(Context context, AttributeSet attrs) {
103 | super(context, attrs);
104 | init();
105 | }
106 |
107 | public PhotoView(Context context, AttributeSet attrs, int defStyleAttr) {
108 | super(context, attrs, defStyleAttr);
109 | init();
110 | }
111 |
112 | private void init() {
113 | super.setScaleType(ScaleType.MATRIX);
114 | if (mScaleType == null) mScaleType = ScaleType.CENTER_INSIDE;
115 | mRotateDetector = new RotateGestureDetector(mRotateListener);
116 | mDetector = new GestureDetector(getContext(), mGestureListener);
117 | mScaleDetector = new ScaleGestureDetector(getContext(), mScaleListener);
118 | float density = getResources().getDisplayMetrics().density;
119 | MAX_OVER_SCROLL = (int) (density * 30);
120 | MAX_FLING_OVER_SCROLL = (int) (density * 30);
121 | MAX_OVER_RESISTANCE = (int) (density * 140);
122 |
123 | mMinRotate = MIN_ROTATE;
124 | mAnimaDuring = ANIMA_DURING;
125 | mMaxScale = MAX_SCALE;
126 | }
127 |
128 | /**
129 | * 获取默认的动画持续时间
130 | */
131 | public int getDefaultAnimaDuring() {
132 | return ANIMA_DURING;
133 | }
134 |
135 | @Override
136 | public void setOnClickListener(OnClickListener l) {
137 | super.setOnClickListener(l);
138 | mClickListener = l;
139 | }
140 |
141 | @Override
142 | public void setScaleType(ScaleType scaleType) {
143 | if (scaleType == ScaleType.MATRIX) return;
144 |
145 | if (scaleType != mScaleType) {
146 | mScaleType = scaleType;
147 |
148 | if (isInit) {
149 | initBase();
150 | }
151 | }
152 | }
153 |
154 | @Override
155 | public void setOnLongClickListener(OnLongClickListener l) {
156 | super.setOnLongClickListener(l);
157 | mLongClick = l;
158 | }
159 |
160 | /**
161 | * 设置动画的插入器
162 | */
163 | public void setInterpolator(Interpolator interpolator) {
164 | mTranslate.setInterpolator(interpolator);
165 | }
166 |
167 | /**
168 | * 获取动画持续时间
169 | */
170 | public int getAnimaDuring() {
171 | return mAnimaDuring;
172 | }
173 |
174 | /**
175 | * 设置动画的持续时间
176 | */
177 | public void setAnimaDuring(int during) {
178 | mAnimaDuring = during;
179 | }
180 |
181 | /**
182 | * 设置最大可以缩放的倍数
183 | */
184 | public void setMaxScale(float maxScale) {
185 | mMaxScale = maxScale;
186 | }
187 |
188 | /**
189 | * 获取最大可以缩放的倍数
190 | */
191 | public float getMaxScale() {
192 | return mMaxScale;
193 | }
194 |
195 | /**
196 | * 启用缩放功能
197 | */
198 | public void enable() {
199 | isEnable = true;
200 | }
201 |
202 | /**
203 | * 禁用缩放功能
204 | */
205 | public void disenable() {
206 | isEnable = false;
207 | }
208 |
209 | /**
210 | * 启用旋转功能
211 | */
212 | public void enableRotate() {
213 | isRotateEnable = true;
214 | }
215 |
216 | /**
217 | * 禁用旋转功能
218 | */
219 | public void disableRotate() {
220 | isRotateEnable = false;
221 | }
222 |
223 | /**
224 | */
225 | public void setMaxAnimFromWaiteTime(int wait) {
226 | MAX_ANIM_FROM_WAITE = wait;
227 | }
228 |
229 | @Override
230 | public void setImageResource(int resId) {
231 | Drawable drawable = null;
232 | try {
233 | drawable = getResources().getDrawable(resId);
234 | } catch (Exception e) {
235 | }
236 |
237 | setImageDrawable(drawable);
238 | }
239 |
240 | @Override
241 | public void setImageDrawable(Drawable drawable) {
242 | super.setImageDrawable(drawable);
243 |
244 | if (drawable == null) {
245 | hasDrawable = false;
246 | return;
247 | }
248 |
249 | if (!hasSize(drawable))
250 | return;
251 |
252 | if (!hasDrawable) {
253 | hasDrawable = true;
254 | }
255 |
256 | initBase();
257 | }
258 |
259 | private boolean hasSize(Drawable d) {
260 | if ((d.getIntrinsicHeight() <= 0 || d.getIntrinsicWidth() <= 0)
261 | && (d.getMinimumWidth() <= 0 || d.getMinimumHeight() <= 0)
262 | && (d.getBounds().width() <= 0 || d.getBounds().height() <= 0)) {
263 | return false;
264 | }
265 | return true;
266 | }
267 |
268 | private static int getDrawableWidth(Drawable d) {
269 | int width = d.getIntrinsicWidth();
270 | if (width <= 0) width = d.getMinimumWidth();
271 | if (width <= 0) width = d.getBounds().width();
272 | return width;
273 | }
274 |
275 | private static int getDrawableHeight(Drawable d) {
276 | int height = d.getIntrinsicHeight();
277 | if (height <= 0) height = d.getMinimumHeight();
278 | if (height <= 0) height = d.getBounds().height();
279 | return height;
280 | }
281 |
282 | private void initBase() {
283 | if (!hasDrawable) return;
284 | if (!isKnowSize) return;
285 |
286 | mBaseMatrix.reset();
287 | mAnimaMatrix.reset();
288 |
289 | isZoonUp = false;
290 |
291 | Drawable img = getDrawable();
292 |
293 | int w = getWidth();
294 | int h = getHeight();
295 | int imgw = getDrawableWidth(img);
296 | int imgh = getDrawableHeight(img);
297 |
298 | mBaseRect.set(0, 0, imgw, imgh);
299 |
300 | // 以图片中心点居中位移
301 | int tx = (w - imgw) / 2;
302 | int ty = (h - imgh) / 2;
303 |
304 | float sx = 1;
305 | float sy = 1;
306 |
307 | // 缩放,默认不超过屏幕大小
308 | if (imgw > w) {
309 | sx = (float) w / imgw;
310 | }
311 |
312 | if (imgh > h) {
313 | sy = (float) h / imgh;
314 | }
315 |
316 | float scale = sx < sy ? sx : sy;
317 |
318 | mBaseMatrix.reset();
319 | mBaseMatrix.postTranslate(tx, ty);
320 | mBaseMatrix.postScale(scale, scale, mScreenCenter.x, mScreenCenter.y);
321 | mBaseMatrix.mapRect(mBaseRect);
322 |
323 | mHalfBaseRectWidth = mBaseRect.width() / 2;
324 | mHalfBaseRectHeight = mBaseRect.height() / 2;
325 |
326 | mScaleCenter.set(mScreenCenter);
327 | mRotateCenter.set(mScaleCenter);
328 |
329 | executeTranslate();
330 |
331 | switch (mScaleType) {
332 | case CENTER:
333 | initCenter();
334 | break;
335 | case CENTER_CROP:
336 | initCenterCrop();
337 | break;
338 | case CENTER_INSIDE:
339 | initCenterInside();
340 | break;
341 | case FIT_CENTER:
342 | initFitCenter();
343 | break;
344 | case FIT_START:
345 | initFitStart();
346 | break;
347 | case FIT_END:
348 | initFitEnd();
349 | break;
350 | case FIT_XY:
351 | initFitXY();
352 | break;
353 | }
354 |
355 | isInit = true;
356 |
357 | if (mFromInfo != null && System.currentTimeMillis() - mInfoTime < MAX_ANIM_FROM_WAITE) {
358 | animaFrom(mFromInfo);
359 | }
360 |
361 | mFromInfo = null;
362 | }
363 |
364 | private void initCenter() {
365 | if (!hasDrawable) return;
366 | if (!isKnowSize) return;
367 |
368 | Drawable img = getDrawable();
369 |
370 | int imgw = getDrawableWidth(img);
371 | int imgh = getDrawableHeight(img);
372 |
373 | if (imgw > mWidgetRect.width() || imgh > mWidgetRect.height()) {
374 | float scaleX = imgw / mImgRect.width();
375 | float scaleY = imgh / mImgRect.height();
376 |
377 | mScale = scaleX > scaleY ? scaleX : scaleY;
378 |
379 | mAnimaMatrix.postScale(mScale, mScale, mScreenCenter.x, mScreenCenter.y);
380 |
381 | executeTranslate();
382 |
383 | resetBase();
384 | }
385 | }
386 |
387 | private void initCenterCrop() {
388 | if (mImgRect.width() < mWidgetRect.width() || mImgRect.height() < mWidgetRect.height()) {
389 | float scaleX = mWidgetRect.width() / mImgRect.width();
390 | float scaleY = mWidgetRect.height() / mImgRect.height();
391 |
392 | mScale = scaleX > scaleY ? scaleX : scaleY;
393 |
394 | mAnimaMatrix.postScale(mScale, mScale, mScreenCenter.x, mScreenCenter.y);
395 |
396 | executeTranslate();
397 | resetBase();
398 | }
399 | }
400 |
401 | private void initCenterInside() {
402 | if (mImgRect.width() > mWidgetRect.width() || mImgRect.height() > mWidgetRect.height()) {
403 | float scaleX = mWidgetRect.width() / mImgRect.width();
404 | float scaleY = mWidgetRect.height() / mImgRect.height();
405 |
406 | mScale = scaleX < scaleY ? scaleX : scaleY;
407 |
408 | mAnimaMatrix.postScale(mScale, mScale, mScreenCenter.x, mScreenCenter.y);
409 |
410 | executeTranslate();
411 | resetBase();
412 | }
413 | }
414 |
415 | private void initFitCenter() {
416 | if (mImgRect.width() < mWidgetRect.width()) {
417 | mScale = mWidgetRect.width() / mImgRect.width();
418 |
419 | mAnimaMatrix.postScale(mScale, mScale, mScreenCenter.x, mScreenCenter.y);
420 |
421 | executeTranslate();
422 | resetBase();
423 | }
424 | }
425 |
426 | private void initFitStart() {
427 | initFitCenter();
428 |
429 | float ty = -mImgRect.top;
430 | mAnimaMatrix.postTranslate(0, ty);
431 | executeTranslate();
432 | resetBase();
433 | mTranslateY += ty;
434 | }
435 |
436 | private void initFitEnd() {
437 | initFitCenter();
438 |
439 | float ty = (mWidgetRect.bottom - mImgRect.bottom);
440 | mTranslateY += ty;
441 | mAnimaMatrix.postTranslate(0, ty);
442 | executeTranslate();
443 | resetBase();
444 | }
445 |
446 | private void initFitXY() {
447 | float scaleX = mWidgetRect.width() / mImgRect.width();
448 | float scaleY = mWidgetRect.height() / mImgRect.height();
449 |
450 | mAnimaMatrix.postScale(scaleX, scaleY, mScreenCenter.x, mScreenCenter.y);
451 |
452 | executeTranslate();
453 | resetBase();
454 | }
455 |
456 | private void resetBase() {
457 | Drawable img = getDrawable();
458 | int imgw = getDrawableWidth(img);
459 | int imgh = getDrawableHeight(img);
460 | mBaseRect.set(0, 0, imgw, imgh);
461 | mBaseMatrix.set(mSynthesisMatrix);
462 | mBaseMatrix.mapRect(mBaseRect);
463 | mHalfBaseRectWidth = mBaseRect.width() / 2;
464 | mHalfBaseRectHeight = mBaseRect.height() / 2;
465 | mScale = 1;
466 | mTranslateX = 0;
467 | mTranslateY = 0;
468 | mAnimaMatrix.reset();
469 | }
470 |
471 | private void executeTranslate() {
472 | mSynthesisMatrix.set(mBaseMatrix);
473 | mSynthesisMatrix.postConcat(mAnimaMatrix);
474 | setImageMatrix(mSynthesisMatrix);
475 |
476 | mAnimaMatrix.mapRect(mImgRect, mBaseRect);
477 |
478 | imgLargeWidth = mImgRect.width() > mWidgetRect.width();
479 | imgLargeHeight = mImgRect.height() > mWidgetRect.height();
480 | }
481 |
482 | @Override
483 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
484 | if (!hasDrawable) {
485 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
486 | return;
487 | }
488 |
489 | Drawable d = getDrawable();
490 | int drawableW = getDrawableWidth(d);
491 | int drawableH = getDrawableHeight(d);
492 |
493 | int pWidth = MeasureSpec.getSize(widthMeasureSpec);
494 | int pHeight = MeasureSpec.getSize(heightMeasureSpec);
495 |
496 | int widthMode = MeasureSpec.getMode(widthMeasureSpec);
497 | int heightMode = MeasureSpec.getMode(heightMeasureSpec);
498 |
499 | int width = 0;
500 | int height = 0;
501 |
502 | ViewGroup.LayoutParams p = getLayoutParams();
503 |
504 | if (p == null) {
505 | p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
506 | }
507 |
508 | if (p.width == ViewGroup.LayoutParams.MATCH_PARENT) {
509 | if (widthMode == MeasureSpec.UNSPECIFIED) {
510 | width = drawableW;
511 | } else {
512 | width = pWidth;
513 | }
514 | } else {
515 | if (widthMode == MeasureSpec.EXACTLY) {
516 | width = pWidth;
517 | } else if (widthMode == MeasureSpec.AT_MOST) {
518 | width = drawableW > pWidth ? pWidth : drawableW;
519 | } else {
520 | width = drawableW;
521 | }
522 | }
523 |
524 | if (p.height == ViewGroup.LayoutParams.MATCH_PARENT) {
525 | if (heightMode == MeasureSpec.UNSPECIFIED) {
526 | height = drawableH;
527 | } else {
528 | height = pHeight;
529 | }
530 | } else {
531 | if (heightMode == MeasureSpec.EXACTLY) {
532 | height = pHeight;
533 | } else if (heightMode == MeasureSpec.AT_MOST) {
534 | height = drawableH > pHeight ? pHeight : drawableH;
535 | } else {
536 | height = drawableH;
537 | }
538 | }
539 |
540 | if (mAdjustViewBounds && (float) drawableW / drawableH != (float) width / height) {
541 |
542 | float hScale = (float) height / drawableH;
543 | float wScale = (float) width / drawableW;
544 |
545 | float scale = hScale < wScale ? hScale : wScale;
546 | width = p.width == ViewGroup.LayoutParams.MATCH_PARENT ? width : (int) (drawableW * scale);
547 | height = p.height == ViewGroup.LayoutParams.MATCH_PARENT ? height : (int) (drawableH * scale);
548 | }
549 |
550 | setMeasuredDimension(width, height);
551 | }
552 |
553 | @Override
554 | public void setAdjustViewBounds(boolean adjustViewBounds) {
555 | super.setAdjustViewBounds(adjustViewBounds);
556 | mAdjustViewBounds = adjustViewBounds;
557 | }
558 |
559 | @Override
560 | protected void onSizeChanged(int w, int h, int oldw, int oldh) {
561 | super.onSizeChanged(w, h, oldw, oldh);
562 |
563 | mWidgetRect.set(0, 0, w, h);
564 | mScreenCenter.set(w / 2, h / 2);
565 |
566 | if (!isKnowSize) {
567 | isKnowSize = true;
568 | initBase();
569 | }
570 | }
571 |
572 | @Override
573 | public void draw(Canvas canvas) {
574 | if (mClip != null) {
575 | canvas.clipRect(mClip);
576 | mClip = null;
577 | }
578 | super.draw(canvas);
579 | }
580 |
581 | @Override
582 | public boolean dispatchTouchEvent(MotionEvent event) {
583 | if (isEnable) {
584 | final int Action = event.getActionMasked();
585 | if (event.getPointerCount() >= 2) hasMultiTouch = true;
586 |
587 | mDetector.onTouchEvent(event);
588 | if (isRotateEnable) {
589 | mRotateDetector.onTouchEvent(event);
590 | }
591 | mScaleDetector.onTouchEvent(event);
592 |
593 | if (Action == MotionEvent.ACTION_UP || Action == MotionEvent.ACTION_CANCEL) onUp();
594 |
595 | return true;
596 | } else {
597 | return super.dispatchTouchEvent(event);
598 | }
599 | }
600 |
601 | private void onUp() {
602 | if (mTranslate.isRuning) return;
603 |
604 | if (canRotate || mDegrees % 90 != 0) {
605 | float toDegrees = (int) (mDegrees / 90) * 90;
606 | float remainder = mDegrees % 90;
607 |
608 | if (remainder > 45)
609 | toDegrees += 90;
610 | else if (remainder < -45)
611 | toDegrees -= 90;
612 |
613 | mTranslate.withRotate((int) mDegrees, (int) toDegrees);
614 |
615 | mDegrees = toDegrees;
616 | }
617 |
618 | float scale = mScale;
619 |
620 | if (mScale < 1) {
621 | scale = 1;
622 | mTranslate.withScale(mScale, 1);
623 | } else if (mScale > mMaxScale) {
624 | scale = mMaxScale;
625 | mTranslate.withScale(mScale, mMaxScale);
626 | }
627 |
628 | float cx = mImgRect.left + mImgRect.width() / 2;
629 | float cy = mImgRect.top + mImgRect.height() / 2;
630 |
631 | mScaleCenter.set(cx, cy);
632 | mRotateCenter.set(cx, cy);
633 |
634 | mTranslateX = 0;
635 | mTranslateY = 0;
636 |
637 | mTmpMatrix.reset();
638 | mTmpMatrix.postTranslate(-mBaseRect.left, -mBaseRect.top);
639 | mTmpMatrix.postTranslate(cx - mHalfBaseRectWidth, cy - mHalfBaseRectHeight);
640 | mTmpMatrix.postScale(scale, scale, cx, cy);
641 | mTmpMatrix.postRotate(mDegrees, cx, cy);
642 | mTmpMatrix.mapRect(mTmpRect, mBaseRect);
643 |
644 | doTranslateReset(mTmpRect);
645 | mTranslate.start();
646 | }
647 |
648 | private void doTranslateReset(RectF imgRect) {
649 | int tx = 0;
650 | int ty = 0;
651 |
652 | if (imgRect.width() <= mWidgetRect.width()) {
653 | if (!isImageCenterWidth(imgRect))
654 | tx = -(int) ((mWidgetRect.width() - imgRect.width()) / 2 - imgRect.left);
655 | } else {
656 | if (imgRect.left > mWidgetRect.left) {
657 | tx = (int) (imgRect.left - mWidgetRect.left);
658 | } else if (imgRect.right < mWidgetRect.right) {
659 | tx = (int) (imgRect.right - mWidgetRect.right);
660 | }
661 | }
662 |
663 | if (imgRect.height() <= mWidgetRect.height()) {
664 | if (!isImageCenterHeight(imgRect))
665 | ty = -(int) ((mWidgetRect.height() - imgRect.height()) / 2 - imgRect.top);
666 | } else {
667 | if (imgRect.top > mWidgetRect.top) {
668 | ty = (int) (imgRect.top - mWidgetRect.top);
669 | } else if (imgRect.bottom < mWidgetRect.bottom) {
670 | ty = (int) (imgRect.bottom - mWidgetRect.bottom);
671 | }
672 | }
673 |
674 | if (tx != 0 || ty != 0) {
675 | if (!mTranslate.mFlingScroller.isFinished()) mTranslate.mFlingScroller.abortAnimation();
676 | mTranslate.withTranslate(mTranslateX, mTranslateY, -tx, -ty);
677 | }
678 | }
679 |
680 | private boolean isImageCenterHeight(RectF rect) {
681 | return Math.abs(Math.round(rect.top) - (mWidgetRect.height() - rect.height()) / 2) < 1;
682 | }
683 |
684 | private boolean isImageCenterWidth(RectF rect) {
685 | return Math.abs(Math.round(rect.left) - (mWidgetRect.width() - rect.width()) / 2) < 1;
686 | }
687 |
688 | private OnRotateListener mRotateListener = new OnRotateListener() {
689 |
690 | @Override
691 | public void onRotate(float degrees, float focusX, float focusY) {
692 | mRotateFlag += degrees;
693 | if (canRotate) {
694 | mDegrees += degrees;
695 | mAnimaMatrix.postRotate(degrees, focusX, focusY);
696 | } else {
697 | if (Math.abs(mRotateFlag) >= mMinRotate) {
698 | canRotate = true;
699 | mRotateFlag = 0;
700 | }
701 | }
702 | }
703 | };
704 |
705 | private ScaleGestureDetector.OnScaleGestureListener mScaleListener = new ScaleGestureDetector.OnScaleGestureListener() {
706 | @Override
707 | public boolean onScale(ScaleGestureDetector detector) {
708 | float scaleFactor = detector.getScaleFactor();
709 |
710 | if (Float.isNaN(scaleFactor) || Float.isInfinite(scaleFactor))
711 | return false;
712 |
713 | mScale *= scaleFactor;
714 | // mScaleCenter.set(detector.getFocusX(), detector.getFocusY());
715 | mAnimaMatrix.postScale(scaleFactor, scaleFactor, detector.getFocusX(), detector.getFocusY());
716 | executeTranslate();
717 | return true;
718 | }
719 |
720 | public boolean onScaleBegin(ScaleGestureDetector detector) {
721 | return true;
722 | }
723 |
724 | public void onScaleEnd(ScaleGestureDetector detector) {
725 |
726 | }
727 | };
728 |
729 | private float resistanceScrollByX(float overScroll, float detalX) {
730 | float s = detalX * (Math.abs(Math.abs(overScroll) - MAX_OVER_RESISTANCE) / (float) MAX_OVER_RESISTANCE);
731 | return s;
732 | }
733 |
734 | private float resistanceScrollByY(float overScroll, float detalY) {
735 | float s = detalY * (Math.abs(Math.abs(overScroll) - MAX_OVER_RESISTANCE) / (float) MAX_OVER_RESISTANCE);
736 | return s;
737 | }
738 |
739 | /**
740 | * 匹配两个Rect的共同部分输出到out,若无共同部分则输出0,0,0,0
741 | */
742 | private void mapRect(RectF r1, RectF r2, RectF out) {
743 |
744 | float l, r, t, b;
745 |
746 | l = r1.left > r2.left ? r1.left : r2.left;
747 | r = r1.right < r2.right ? r1.right : r2.right;
748 |
749 | if (l > r) {
750 | out.set(0, 0, 0, 0);
751 | return;
752 | }
753 |
754 | t = r1.top > r2.top ? r1.top : r2.top;
755 | b = r1.bottom < r2.bottom ? r1.bottom : r2.bottom;
756 |
757 | if (t > b) {
758 | out.set(0, 0, 0, 0);
759 | return;
760 | }
761 |
762 | out.set(l, t, r, b);
763 | }
764 |
765 | private void checkRect() {
766 | if (!hasOverTranslate) {
767 | mapRect(mWidgetRect, mImgRect, mCommonRect);
768 | }
769 | }
770 |
771 | private Runnable mClickRunnable = new Runnable() {
772 | @Override
773 | public void run() {
774 | if (mClickListener != null) {
775 | mClickListener.onClick(PhotoView.this);
776 | }
777 | }
778 | };
779 |
780 | private GestureDetector.OnGestureListener mGestureListener = new GestureDetector.SimpleOnGestureListener() {
781 |
782 | @Override
783 | public void onLongPress(MotionEvent e) {
784 | if (mLongClick != null) {
785 | mLongClick.onLongClick(PhotoView.this);
786 | }
787 | }
788 |
789 | @Override
790 | public boolean onDown(MotionEvent e) {
791 | hasOverTranslate = false;
792 | hasMultiTouch = false;
793 | canRotate = false;
794 | removeCallbacks(mClickRunnable);
795 | return false;
796 | }
797 |
798 | @Override
799 | public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
800 | if (hasMultiTouch) return false;
801 | if (!imgLargeWidth && !imgLargeHeight) return false;
802 | if (mTranslate.isRuning) return false;
803 |
804 | float vx = velocityX;
805 | float vy = velocityY;
806 |
807 | if (Math.round(mImgRect.left) >= mWidgetRect.left || Math.round(mImgRect.right) <= mWidgetRect.right) {
808 | vx = 0;
809 | }
810 |
811 | if (Math.round(mImgRect.top) >= mWidgetRect.top || Math.round(mImgRect.bottom) <= mWidgetRect.bottom) {
812 | vy = 0;
813 | }
814 |
815 | if (canRotate || mDegrees % 90 != 0) {
816 | float toDegrees = (int) (mDegrees / 90) * 90;
817 | float remainder = mDegrees % 90;
818 |
819 | if (remainder > 45)
820 | toDegrees += 90;
821 | else if (remainder < -45)
822 | toDegrees -= 90;
823 |
824 | mTranslate.withRotate((int) mDegrees, (int) toDegrees);
825 |
826 | mDegrees = toDegrees;
827 | }
828 |
829 | doTranslateReset(mImgRect);
830 |
831 | mTranslate.withFling(vx, vy);
832 |
833 | mTranslate.start();
834 | // onUp(e2);
835 | return super.onFling(e1, e2, velocityX, velocityY);
836 | }
837 |
838 | @Override
839 | public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
840 | if (mTranslate.isRuning) {
841 | mTranslate.stop();
842 | }
843 |
844 | if (canScrollHorizontallySelf(distanceX)) {
845 | if (distanceX < 0 && mImgRect.left - distanceX > mWidgetRect.left)
846 | distanceX = mImgRect.left;
847 | if (distanceX > 0 && mImgRect.right - distanceX < mWidgetRect.right)
848 | distanceX = mImgRect.right - mWidgetRect.right;
849 |
850 | mAnimaMatrix.postTranslate(-distanceX, 0);
851 | mTranslateX -= distanceX;
852 | } else if (imgLargeWidth || hasMultiTouch || hasOverTranslate) {
853 | checkRect();
854 | if (!hasMultiTouch) {
855 | if (distanceX < 0 && mImgRect.left - distanceX > mCommonRect.left)
856 | distanceX = resistanceScrollByX(mImgRect.left - mCommonRect.left, distanceX);
857 | if (distanceX > 0 && mImgRect.right - distanceX < mCommonRect.right)
858 | distanceX = resistanceScrollByX(mImgRect.right - mCommonRect.right, distanceX);
859 | }
860 |
861 | mTranslateX -= distanceX;
862 | mAnimaMatrix.postTranslate(-distanceX, 0);
863 | hasOverTranslate = true;
864 | }
865 |
866 | if (canScrollVerticallySelf(distanceY)) {
867 | if (distanceY < 0 && mImgRect.top - distanceY > mWidgetRect.top)
868 | distanceY = mImgRect.top;
869 | if (distanceY > 0 && mImgRect.bottom - distanceY < mWidgetRect.bottom)
870 | distanceY = mImgRect.bottom - mWidgetRect.bottom;
871 |
872 | mAnimaMatrix.postTranslate(0, -distanceY);
873 | mTranslateY -= distanceY;
874 | } else if (imgLargeHeight || hasOverTranslate || hasMultiTouch) {
875 | checkRect();
876 | if (!hasMultiTouch) {
877 | if (distanceY < 0 && mImgRect.top - distanceY > mCommonRect.top)
878 | distanceY = resistanceScrollByY(mImgRect.top - mCommonRect.top, distanceY);
879 | if (distanceY > 0 && mImgRect.bottom - distanceY < mCommonRect.bottom)
880 | distanceY = resistanceScrollByY(mImgRect.bottom - mCommonRect.bottom, distanceY);
881 | }
882 |
883 | mAnimaMatrix.postTranslate(0, -distanceY);
884 | mTranslateY -= distanceY;
885 | hasOverTranslate = true;
886 | }
887 |
888 | executeTranslate();
889 | return true;
890 | }
891 |
892 | @Override
893 | public boolean onSingleTapUp(MotionEvent e) {
894 | postDelayed(mClickRunnable, 250);
895 | return false;
896 | }
897 |
898 | @Override
899 | public boolean onDoubleTap(MotionEvent e) {
900 |
901 | mTranslate.stop();
902 |
903 | float from = 1;
904 | float to = 1;
905 |
906 | float imgcx = mImgRect.left + mImgRect.width() / 2;
907 | float imgcy = mImgRect.top + mImgRect.height() / 2;
908 |
909 | mScaleCenter.set(imgcx, imgcy);
910 | mRotateCenter.set(imgcx, imgcy);
911 | mTranslateX = 0;
912 | mTranslateY = 0;
913 |
914 | if (isZoonUp) {
915 | from = mScale;
916 | to = 1;
917 | } else {
918 | from = mScale;
919 | to = mMaxScale;
920 |
921 | mScaleCenter.set(e.getX(), e.getY());
922 | }
923 |
924 | mTmpMatrix.reset();
925 | mTmpMatrix.postTranslate(-mBaseRect.left, -mBaseRect.top);
926 | mTmpMatrix.postTranslate(mRotateCenter.x, mRotateCenter.y);
927 | mTmpMatrix.postTranslate(-mHalfBaseRectWidth, -mHalfBaseRectHeight);
928 | mTmpMatrix.postRotate(mDegrees, mRotateCenter.x, mRotateCenter.y);
929 | mTmpMatrix.postScale(to, to, mScaleCenter.x, mScaleCenter.y);
930 | mTmpMatrix.postTranslate(mTranslateX, mTranslateY);
931 | mTmpMatrix.mapRect(mTmpRect, mBaseRect);
932 | doTranslateReset(mTmpRect);
933 |
934 | isZoonUp = !isZoonUp;
935 | mTranslate.withScale(from, to);
936 | mTranslate.start();
937 |
938 | return false;
939 | }
940 | };
941 |
942 | public boolean canScrollHorizontallySelf(float direction) {
943 | if (mImgRect.width() <= mWidgetRect.width()) return false;
944 | if (direction < 0 && Math.round(mImgRect.left) - direction >= mWidgetRect.left)
945 | return false;
946 | if (direction > 0 && Math.round(mImgRect.right) - direction <= mWidgetRect.right)
947 | return false;
948 | return true;
949 | }
950 |
951 | public boolean canScrollVerticallySelf(float direction) {
952 | if (mImgRect.height() <= mWidgetRect.height()) return false;
953 | if (direction < 0 && Math.round(mImgRect.top) - direction >= mWidgetRect.top)
954 | return false;
955 | if (direction > 0 && Math.round(mImgRect.bottom) - direction <= mWidgetRect.bottom)
956 | return false;
957 | return true;
958 | }
959 |
960 | @Override
961 | public boolean canScrollHorizontally(int direction) {
962 | if (hasMultiTouch) return true;
963 | return canScrollHorizontallySelf(direction);
964 | }
965 |
966 | @Override
967 | public boolean canScrollVertically(int direction) {
968 | if (hasMultiTouch) return true;
969 | return canScrollVerticallySelf(direction);
970 | }
971 |
972 | private class InterpolatorProxy implements Interpolator {
973 |
974 | private Interpolator mTarget;
975 |
976 | private InterpolatorProxy() {
977 | mTarget = new DecelerateInterpolator();
978 | }
979 |
980 | public void setTargetInterpolator(Interpolator interpolator) {
981 | mTarget = interpolator;
982 | }
983 |
984 | @Override
985 | public float getInterpolation(float input) {
986 | if (mTarget != null) {
987 | return mTarget.getInterpolation(input);
988 | }
989 | return input;
990 | }
991 | }
992 |
993 | private class Transform implements Runnable {
994 |
995 | boolean isRuning;
996 |
997 | OverScroller mTranslateScroller;
998 | OverScroller mFlingScroller;
999 | Scroller mScaleScroller;
1000 | Scroller mClipScroller;
1001 | Scroller mRotateScroller;
1002 |
1003 | ClipCalculate C;
1004 |
1005 | int mLastFlingX;
1006 | int mLastFlingY;
1007 |
1008 | int mLastTranslateX;
1009 | int mLastTranslateY;
1010 |
1011 | RectF mClipRect = new RectF();
1012 |
1013 | InterpolatorProxy mInterpolatorProxy = new InterpolatorProxy();
1014 |
1015 | Transform() {
1016 | Context ctx = getContext();
1017 | mTranslateScroller = new OverScroller(ctx, mInterpolatorProxy);
1018 | mScaleScroller = new Scroller(ctx, mInterpolatorProxy);
1019 | mFlingScroller = new OverScroller(ctx, mInterpolatorProxy);
1020 | mClipScroller = new Scroller(ctx, mInterpolatorProxy);
1021 | mRotateScroller = new Scroller(ctx, mInterpolatorProxy);
1022 | }
1023 |
1024 | public void setInterpolator(Interpolator interpolator) {
1025 | mInterpolatorProxy.setTargetInterpolator(interpolator);
1026 | }
1027 |
1028 | void withTranslate(int startX, int startY, int deltaX, int deltaY) {
1029 | mLastTranslateX = 0;
1030 | mLastTranslateY = 0;
1031 | mTranslateScroller.startScroll(0, 0, deltaX, deltaY, mAnimaDuring);
1032 | }
1033 |
1034 | void withScale(float form, float to) {
1035 | mScaleScroller.startScroll((int) (form * 10000), 0, (int) ((to - form) * 10000), 0, mAnimaDuring);
1036 | }
1037 |
1038 | void withClip(float fromX, float fromY, float deltaX, float deltaY, int d, ClipCalculate c) {
1039 | mClipScroller.startScroll((int) (fromX * 10000), (int) (fromY * 10000), (int) (deltaX * 10000), (int) (deltaY * 10000), d);
1040 | C = c;
1041 | }
1042 |
1043 | void withRotate(int fromDegrees, int toDegrees) {
1044 | mRotateScroller.startScroll(fromDegrees, 0, toDegrees - fromDegrees, 0, mAnimaDuring);
1045 | }
1046 |
1047 | void withRotate(int fromDegrees, int toDegrees, int during) {
1048 | mRotateScroller.startScroll(fromDegrees, 0, toDegrees - fromDegrees, 0, during);
1049 | }
1050 |
1051 | void withFling(float velocityX, float velocityY) {
1052 | mLastFlingX = velocityX < 0 ? Integer.MAX_VALUE : 0;
1053 | int distanceX = (int) (velocityX > 0 ? Math.abs(mImgRect.left) : mImgRect.right - mWidgetRect.right);
1054 | distanceX = velocityX < 0 ? Integer.MAX_VALUE - distanceX : distanceX;
1055 | int minX = velocityX < 0 ? distanceX : 0;
1056 | int maxX = velocityX < 0 ? Integer.MAX_VALUE : distanceX;
1057 | int overX = velocityX < 0 ? Integer.MAX_VALUE - minX : distanceX;
1058 |
1059 | mLastFlingY = velocityY < 0 ? Integer.MAX_VALUE : 0;
1060 | int distanceY = (int) (velocityY > 0 ? Math.abs(mImgRect.top) : mImgRect.bottom - mWidgetRect.bottom);
1061 | distanceY = velocityY < 0 ? Integer.MAX_VALUE - distanceY : distanceY;
1062 | int minY = velocityY < 0 ? distanceY : 0;
1063 | int maxY = velocityY < 0 ? Integer.MAX_VALUE : distanceY;
1064 | int overY = velocityY < 0 ? Integer.MAX_VALUE - minY : distanceY;
1065 |
1066 | if (velocityX == 0) {
1067 | maxX = 0;
1068 | minX = 0;
1069 | }
1070 |
1071 | if (velocityY == 0) {
1072 | maxY = 0;
1073 | minY = 0;
1074 | }
1075 |
1076 | mFlingScroller.fling(mLastFlingX, mLastFlingY, (int) velocityX, (int) velocityY, minX, maxX, minY, maxY, Math.abs(overX) < MAX_FLING_OVER_SCROLL * 2 ? 0 : MAX_FLING_OVER_SCROLL, Math.abs(overY) < MAX_FLING_OVER_SCROLL * 2 ? 0 : MAX_FLING_OVER_SCROLL);
1077 | }
1078 |
1079 | void start() {
1080 | isRuning = true;
1081 | postExecute();
1082 | }
1083 |
1084 | void stop() {
1085 | removeCallbacks(this);
1086 | mTranslateScroller.abortAnimation();
1087 | mScaleScroller.abortAnimation();
1088 | mFlingScroller.abortAnimation();
1089 | mRotateScroller.abortAnimation();
1090 | isRuning = false;
1091 | }
1092 |
1093 | @Override
1094 | public void run() {
1095 |
1096 | // if (!isRuning) return;
1097 |
1098 | boolean endAnima = true;
1099 |
1100 | if (mScaleScroller.computeScrollOffset()) {
1101 | mScale = mScaleScroller.getCurrX() / 10000f;
1102 | endAnima = false;
1103 | }
1104 |
1105 | if (mTranslateScroller.computeScrollOffset()) {
1106 | int tx = mTranslateScroller.getCurrX() - mLastTranslateX;
1107 | int ty = mTranslateScroller.getCurrY() - mLastTranslateY;
1108 | mTranslateX += tx;
1109 | mTranslateY += ty;
1110 | mLastTranslateX = mTranslateScroller.getCurrX();
1111 | mLastTranslateY = mTranslateScroller.getCurrY();
1112 | endAnima = false;
1113 | }
1114 |
1115 | if (mFlingScroller.computeScrollOffset()) {
1116 | int x = mFlingScroller.getCurrX() - mLastFlingX;
1117 | int y = mFlingScroller.getCurrY() - mLastFlingY;
1118 |
1119 | mLastFlingX = mFlingScroller.getCurrX();
1120 | mLastFlingY = mFlingScroller.getCurrY();
1121 |
1122 | mTranslateX += x;
1123 | mTranslateY += y;
1124 | endAnima = false;
1125 | }
1126 |
1127 | if (mRotateScroller.computeScrollOffset()) {
1128 | mDegrees = mRotateScroller.getCurrX();
1129 | endAnima = false;
1130 | }
1131 |
1132 | if (mClipScroller.computeScrollOffset() || mClip != null) {
1133 | float sx = mClipScroller.getCurrX() / 10000f;
1134 | float sy = mClipScroller.getCurrY() / 10000f;
1135 | mTmpMatrix.setScale(sx, sy, (mImgRect.left + mImgRect.right) / 2, C.calculateTop());
1136 | mTmpMatrix.mapRect(mClipRect, mImgRect);
1137 |
1138 | if (sx == 1) {
1139 | mClipRect.left = mWidgetRect.left;
1140 | mClipRect.right = mWidgetRect.right;
1141 | }
1142 |
1143 | if (sy == 1) {
1144 | mClipRect.top = mWidgetRect.top;
1145 | mClipRect.bottom = mWidgetRect.bottom;
1146 | }
1147 |
1148 | mClip = mClipRect;
1149 | }
1150 |
1151 | if (!endAnima) {
1152 | applyAnima();
1153 | postExecute();
1154 | } else {
1155 | isRuning = false;
1156 |
1157 | // 修复动画结束后边距有些空隙,
1158 | boolean needFix = false;
1159 |
1160 | if (imgLargeWidth) {
1161 | if (mImgRect.left > 0) {
1162 | mTranslateX -= mImgRect.left;
1163 | } else if (mImgRect.right < mWidgetRect.width()) {
1164 | mTranslateX -= (int) (mWidgetRect.width() - mImgRect.right);
1165 | }
1166 | needFix = true;
1167 | }
1168 |
1169 | if (imgLargeHeight) {
1170 | if (mImgRect.top > 0) {
1171 | mTranslateY -= mImgRect.top;
1172 | } else if (mImgRect.bottom < mWidgetRect.height()) {
1173 | mTranslateY -= (int) (mWidgetRect.height() - mImgRect.bottom);
1174 | }
1175 | needFix = true;
1176 | }
1177 |
1178 | if (needFix) {
1179 | applyAnima();
1180 | }
1181 |
1182 | invalidate();
1183 |
1184 | if (mCompleteCallBack != null) {
1185 | mCompleteCallBack.run();
1186 | mCompleteCallBack = null;
1187 | }
1188 | }
1189 | }
1190 |
1191 | private void applyAnima() {
1192 | mAnimaMatrix.reset();
1193 | mAnimaMatrix.postTranslate(-mBaseRect.left, -mBaseRect.top);
1194 | mAnimaMatrix.postTranslate(mRotateCenter.x, mRotateCenter.y);
1195 | mAnimaMatrix.postTranslate(-mHalfBaseRectWidth, -mHalfBaseRectHeight);
1196 | mAnimaMatrix.postRotate(mDegrees, mRotateCenter.x, mRotateCenter.y);
1197 | mAnimaMatrix.postScale(mScale, mScale, mScaleCenter.x, mScaleCenter.y);
1198 | mAnimaMatrix.postTranslate(mTranslateX, mTranslateY);
1199 | executeTranslate();
1200 | }
1201 |
1202 |
1203 | private void postExecute() {
1204 | if (isRuning) post(this);
1205 | }
1206 | }
1207 |
1208 | public Info getInfo() {
1209 | RectF rect = new RectF();
1210 | int[] p = new int[2];
1211 | getLocation(this, p);
1212 | rect.set(p[0] + mImgRect.left, p[1] + mImgRect.top, p[0] + mImgRect.right, p[1] + mImgRect.bottom);
1213 | return new Info(rect, mImgRect, mWidgetRect, mBaseRect, mScreenCenter, mScale, mDegrees, mScaleType);
1214 | }
1215 |
1216 | public static Info getImageViewInfo(ImageView imgView) {
1217 | int[] p = new int[2];
1218 | getLocation(imgView, p);
1219 |
1220 | Drawable drawable = imgView.getDrawable();
1221 |
1222 | Matrix matrix = imgView.getImageMatrix();
1223 |
1224 | int width = getDrawableWidth(drawable);
1225 | int height = getDrawableHeight(drawable);
1226 |
1227 | RectF imgRect = new RectF(0, 0, width, height);
1228 | matrix.mapRect(imgRect);
1229 |
1230 | RectF rect = new RectF(p[0] + imgRect.left, p[1] + imgRect.top, p[0] + imgRect.right, p[1] + imgRect.bottom);
1231 | RectF widgetRect = new RectF(0, 0, imgView.getWidth(), imgView.getHeight());
1232 | RectF baseRect = new RectF(widgetRect);
1233 | PointF screenCenter = new PointF(widgetRect.width() / 2, widgetRect.height() / 2);
1234 |
1235 | return new Info(rect, imgRect, widgetRect, baseRect, screenCenter, 1, 0, imgView.getScaleType());
1236 | }
1237 |
1238 | private static void getLocation(View target, int[] position) {
1239 |
1240 | position[0] += target.getLeft();
1241 | position[1] += target.getTop();
1242 |
1243 | ViewParent viewParent = target.getParent();
1244 | while (viewParent instanceof View) {
1245 | final View view = (View) viewParent;
1246 |
1247 | if (view.getId() == android.R.id.content) return;
1248 |
1249 | position[0] -= view.getScrollX();
1250 | position[1] -= view.getScrollY();
1251 |
1252 | position[0] += view.getLeft();
1253 | position[1] += view.getTop();
1254 |
1255 | viewParent = view.getParent();
1256 | }
1257 |
1258 | position[0] = (int) (position[0] + 0.5f);
1259 | position[1] = (int) (position[1] + 0.5f);
1260 | }
1261 |
1262 | private void reset() {
1263 | mAnimaMatrix.reset();
1264 | executeTranslate();
1265 | mScale = 1;
1266 | mTranslateX = 0;
1267 | mTranslateY = 0;
1268 | }
1269 |
1270 | public interface ClipCalculate {
1271 | float calculateTop();
1272 | }
1273 |
1274 | public class START implements ClipCalculate {
1275 | public float calculateTop() {
1276 | return mImgRect.top;
1277 | }
1278 | }
1279 |
1280 | public class END implements ClipCalculate {
1281 | public float calculateTop() {
1282 | return mImgRect.bottom;
1283 | }
1284 | }
1285 |
1286 | public class OTHER implements ClipCalculate {
1287 | public float calculateTop() {
1288 | return (mImgRect.top + mImgRect.bottom) / 2;
1289 | }
1290 | }
1291 |
1292 | /**
1293 | * 在PhotoView内部还没有图片的时候同样可以调用该方法
1294 | *
1295 | * 此时并不会播放动画,当给PhotoView设置图片后会自动播放动画。
1296 | *
1297 | * 若等待时间过长也没有给控件设置图片,则会忽略该动画,若要再次播放动画则需要重新调用该方法
1298 | * (等待的时间默认500毫秒,可以通过setMaxAnimFromWaiteTime(int)设置最大等待时间)
1299 | */
1300 | public void animaFrom(Info info) {
1301 | if (isInit) {
1302 | reset();
1303 |
1304 | Info mine = getInfo();
1305 |
1306 | float scaleX = info.mImgRect.width() / mine.mImgRect.width();
1307 | float scaleY = info.mImgRect.height() / mine.mImgRect.height();
1308 | float scale = scaleX < scaleY ? scaleX : scaleY;
1309 |
1310 | float ocx = info.mRect.left + info.mRect.width() / 2;
1311 | float ocy = info.mRect.top + info.mRect.height() / 2;
1312 |
1313 | float mcx = mine.mRect.left + mine.mRect.width() / 2;
1314 | float mcy = mine.mRect.top + mine.mRect.height() / 2;
1315 |
1316 | mAnimaMatrix.reset();
1317 | // mAnimaMatrix.postTranslate(-mBaseRect.left, -mBaseRect.top);
1318 | mAnimaMatrix.postTranslate(ocx - mcx, ocy - mcy);
1319 | mAnimaMatrix.postScale(scale, scale, ocx, ocy);
1320 | mAnimaMatrix.postRotate(info.mDegrees, ocx, ocy);
1321 | executeTranslate();
1322 |
1323 | mScaleCenter.set(ocx, ocy);
1324 | mRotateCenter.set(ocx, ocy);
1325 |
1326 | mTranslate.withTranslate(0, 0, (int) -(ocx - mcx), (int) -(ocy - mcy));
1327 | mTranslate.withScale(scale, 1);
1328 | mTranslate.withRotate((int) info.mDegrees, 0);
1329 |
1330 | if (info.mWidgetRect.width() < info.mImgRect.width() || info.mWidgetRect.height() < info.mImgRect.height()) {
1331 | float clipX = info.mWidgetRect.width() / info.mImgRect.width();
1332 | float clipY = info.mWidgetRect.height() / info.mImgRect.height();
1333 | clipX = clipX > 1 ? 1 : clipX;
1334 | clipY = clipY > 1 ? 1 : clipY;
1335 |
1336 | ClipCalculate c = info.mScaleType == ScaleType.FIT_START ? new START() : info.mScaleType == ScaleType.FIT_END ? new END() : new OTHER();
1337 |
1338 | mTranslate.withClip(clipX, clipY, 1 - clipX, 1 - clipY, mAnimaDuring / 3, c);
1339 |
1340 | mTmpMatrix.setScale(clipX, clipY, (mImgRect.left + mImgRect.right) / 2, c.calculateTop());
1341 | mTmpMatrix.mapRect(mTranslate.mClipRect, mImgRect);
1342 | mClip = mTranslate.mClipRect;
1343 | }
1344 |
1345 | mTranslate.start();
1346 | } else {
1347 | mFromInfo = info;
1348 | mInfoTime = System.currentTimeMillis();
1349 | }
1350 | }
1351 |
1352 | public void animaTo(Info info, Runnable completeCallBack) {
1353 | if (isInit) {
1354 | mTranslate.stop();
1355 |
1356 | mTranslateX = 0;
1357 | mTranslateY = 0;
1358 |
1359 | float tcx = info.mRect.left + info.mRect.width() / 2;
1360 | float tcy = info.mRect.top + info.mRect.height() / 2;
1361 |
1362 | mScaleCenter.set(mImgRect.left + mImgRect.width() / 2, mImgRect.top + mImgRect.height() / 2);
1363 | mRotateCenter.set(mScaleCenter);
1364 |
1365 | // 将图片旋转回正常位置,用以计算
1366 | mAnimaMatrix.postRotate(-mDegrees, mScaleCenter.x, mScaleCenter.y);
1367 | mAnimaMatrix.mapRect(mImgRect, mBaseRect);
1368 |
1369 | // 缩放
1370 | float scaleX = info.mImgRect.width() / mBaseRect.width();
1371 | float scaleY = info.mImgRect.height() / mBaseRect.height();
1372 | float scale = scaleX > scaleY ? scaleX : scaleY;
1373 |
1374 | mAnimaMatrix.postRotate(mDegrees, mScaleCenter.x, mScaleCenter.y);
1375 | mAnimaMatrix.mapRect(mImgRect, mBaseRect);
1376 |
1377 | mDegrees = mDegrees % 360;
1378 |
1379 | mTranslate.withTranslate(0, 0, (int) (tcx - mScaleCenter.x), (int) (tcy - mScaleCenter.y));
1380 | mTranslate.withScale(mScale, scale);
1381 | mTranslate.withRotate((int) mDegrees, (int) info.mDegrees, mAnimaDuring * 2 / 3);
1382 |
1383 | if (info.mWidgetRect.width() < info.mRect.width() || info.mWidgetRect.height() < info.mRect.height()) {
1384 | float clipX = info.mWidgetRect.width() / info.mRect.width();
1385 | float clipY = info.mWidgetRect.height() / info.mRect.height();
1386 | clipX = clipX > 1 ? 1 : clipX;
1387 | clipY = clipY > 1 ? 1 : clipY;
1388 |
1389 | final float cx = clipX;
1390 | final float cy = clipY;
1391 | final ClipCalculate c = info.mScaleType == ScaleType.FIT_START ? new START() : info.mScaleType == ScaleType.FIT_END ? new END() : new OTHER();
1392 |
1393 | postDelayed(new Runnable() {
1394 | @Override
1395 | public void run() {
1396 | mTranslate.withClip(1, 1, -1 + cx, -1 + cy, mAnimaDuring / 2, c);
1397 | }
1398 | }, mAnimaDuring / 2);
1399 | }
1400 |
1401 | mCompleteCallBack = completeCallBack;
1402 | mTranslate.start();
1403 | }
1404 | }
1405 |
1406 | public void rotate(float degrees) {
1407 | mDegrees += degrees;
1408 | int centerX = (int) (mWidgetRect.left + mWidgetRect.width() / 2);
1409 | int centerY = (int) (mWidgetRect.top + mWidgetRect.height() /2);
1410 |
1411 | mAnimaMatrix.postRotate(degrees, centerX, centerY);
1412 | executeTranslate();
1413 | }
1414 | }
1415 |
--------------------------------------------------------------------------------
/library/src/main/java/com/bm/library/RotateGestureDetector.java:
--------------------------------------------------------------------------------
1 | package com.bm.library;
2 |
3 | import android.util.Log;
4 | import android.view.MotionEvent;
5 |
6 | /**
7 | * Created by q2366 on 2015/10/12.
8 | */
9 | public class RotateGestureDetector {
10 |
11 | private static final int MAX_DEGREES_STEP = 120;
12 |
13 | private OnRotateListener mListener;
14 |
15 | private float mPrevSlope;
16 | private float mCurrSlope;
17 |
18 | private float x1;
19 | private float y1;
20 | private float x2;
21 | private float y2;
22 |
23 | public RotateGestureDetector(OnRotateListener l) {
24 | mListener = l;
25 | }
26 |
27 | public void onTouchEvent(MotionEvent event) {
28 |
29 | final int Action = event.getActionMasked();
30 |
31 | switch (Action) {
32 | case MotionEvent.ACTION_POINTER_DOWN:
33 | case MotionEvent.ACTION_POINTER_UP:
34 | if (event.getPointerCount() == 2) mPrevSlope = caculateSlope(event);
35 | break;
36 | case MotionEvent.ACTION_MOVE:
37 | if (event.getPointerCount() > 1) {
38 | mCurrSlope = caculateSlope(event);
39 |
40 | double currDegrees = Math.toDegrees(Math.atan(mCurrSlope));
41 | double prevDegrees = Math.toDegrees(Math.atan(mPrevSlope));
42 |
43 | double deltaSlope = currDegrees - prevDegrees;
44 |
45 | if (Math.abs(deltaSlope) <= MAX_DEGREES_STEP) {
46 | mListener.onRotate((float) deltaSlope, (x2 + x1) / 2, (y2 + y1) / 2);
47 | }
48 | mPrevSlope = mCurrSlope;
49 | }
50 | break;
51 | default:
52 | break;
53 | }
54 | }
55 |
56 | private float caculateSlope(MotionEvent event) {
57 | x1 = event.getX(0);
58 | y1 = event.getY(0);
59 | x2 = event.getX(1);
60 | y2 = event.getY(1);
61 | return (y2 - y1) / (x2 - x1);
62 | }
63 | }
64 |
65 | interface OnRotateListener {
66 | void onRotate(float degrees, float focusX, float focusY);
67 | }
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':library', ':app'
--------------------------------------------------------------------------------