├── .gitignore
├── README.md
├── app
├── 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
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── library
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── itheima
│ │ └── library
│ │ └── ApplicationTest.java
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── itheima
│ └── library
│ ├── Info.java
│ ├── PhotoView.java
│ └── RotateGestureDetector.java
├── local.properties
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | /PhotoView-master.iml
2 | /.idea
3 | /PhotoView.iml
4 | /build
5 | /app/app.iml
6 | /app/build
7 | /library/library.iml
8 | /library/build
9 | /.gradle
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # PhotoView 图片浏览缩放控件
3 |
4 | 和普通的ImageView一样的使用方法
5 |
6 | * 详细的使用方法在DEMO里面都演示啦,如果你觉得这个库还不错,请赏我一颗star吧~~~
7 |
8 | * 欢迎关注微信公众号
9 |
10 | 
11 |
12 |
13 |
14 |
15 |
16 | # 效果图
17 |
18 | 
19 |
20 |
21 |
22 | ### 1. 在你的 project 的 build.gradle 中加入
23 | ```gradle
24 | allprojects {
25 | repositories {
26 |
27 | maven { url 'https://jitpack.io' }
28 | }
29 | }
30 | ```
31 |
32 | ### 2. Gradle添加依赖 (推荐)
33 | ```gradle
34 | dependencies {
35 | compile 'com.github.open-android:PhotoImageView:0.1.0'
36 | }
37 | ```
38 |
39 |
40 | ### 3. xml添加
41 | ```xml
42 |
48 | ```
49 |
50 | ### 4. java代码
51 | ```java
52 | PhotoView photoView = (PhotoView) findViewById(R.id.img);
53 | // 启用图片缩放功能
54 | photoView.enable();
55 | // 禁用图片缩放功能 (默认为禁用,会跟普通的ImageView一样,缩放功能需手动调用enable()启用)
56 | photoView.disenable();
57 | // 获取图片信息
58 | Info info = photoView.getInfo();
59 | // 从普通的ImageView中获取Info
60 | Info info = PhotoView.getImageViewInfo(ImageView);
61 | ```
62 |
63 | ### 高级用法
64 |
65 | 
66 |
67 |
68 | ### 复制如下内容到java代码中
69 |
70 | ```java
71 | public class PhotoBrowse extends Activity {
72 |
73 | int[] imgs = new int[]{R.mipmap.aaa, R.mipmap.bbb, R.mipmap.ccc, R.mipmap.ddd, R.mipmap.ic_launcher, R.mipmap.image003};
74 |
75 | GridView gv;
76 |
77 | View mParent;
78 | View mBg;
79 | PhotoView mPhotoView;
80 | Info mInfo;
81 |
82 | AlphaAnimation in = new AlphaAnimation(0, 1);
83 | AlphaAnimation out = new AlphaAnimation(1, 0);
84 |
85 | @Override
86 | protected void onCreate(Bundle savedInstanceState) {
87 | super.onCreate(savedInstanceState);
88 | requestWindowFeature(Window.FEATURE_NO_TITLE);
89 | setContentView(R.layout.activity_photo_browse);
90 |
91 | in.setDuration(300);
92 | out.setDuration(300);
93 | out.setAnimationListener(new Animation.AnimationListener() {
94 | @Override
95 | public void onAnimationStart(Animation animation) {
96 |
97 | }
98 |
99 | @Override
100 | public void onAnimationEnd(Animation animation) {
101 | mBg.setVisibility(View.INVISIBLE);
102 | }
103 |
104 | @Override
105 | public void onAnimationRepeat(Animation animation) {
106 |
107 | }
108 | });
109 |
110 | mParent = findViewById(R.id.parent);
111 | mBg = findViewById(R.id.bg);
112 | mPhotoView = (PhotoView) findViewById(R.id.img);
113 | mPhotoView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
114 |
115 | gv = (GridView) findViewById(R.id.gv);
116 | gv.setAdapter(new BaseAdapter() {
117 | @Override
118 | public int getCount() {
119 | return imgs.length;
120 | }
121 |
122 | @Override
123 | public Object getItem(int position) {
124 | return null;
125 | }
126 |
127 | @Override
128 | public long getItemId(int position) {
129 | return 0;
130 | }
131 |
132 | @Override
133 | public View getView(int position, View convertView, ViewGroup parent) {
134 | PhotoView p = new PhotoView(PhotoBrowse.this);
135 | p.setLayoutParams(new AbsListView.LayoutParams((int) (getResources().getDisplayMetrics().density * 100), (int) (getResources().getDisplayMetrics().density * 100)));
136 | p.setScaleType(ImageView.ScaleType.CENTER_CROP);
137 | p.setImageResource(imgs[position]);
138 | // 把PhotoView当普通的控件把触摸功能关掉
139 | p.disenable();
140 | return p;
141 | }
142 | });
143 |
144 | gv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
145 | @Override
146 | public void onItemClick(AdapterView> parent, View view, int position, long id) {
147 | PhotoView p = (PhotoView) view;
148 | mInfo = p.getInfo();
149 |
150 | mPhotoView.setImageResource(imgs[position]);
151 | mBg.startAnimation(in);
152 | mBg.setVisibility(View.VISIBLE);
153 | mParent.setVisibility(View.VISIBLE);;
154 | mPhotoView.animaFrom(mInfo);
155 | }
156 | });
157 |
158 | mPhotoView.enable();
159 | mPhotoView.setOnClickListener(new View.OnClickListener() {
160 | @Override
161 | public void onClick(View v) {
162 | mBg.startAnimation(out);
163 | mPhotoView.animaTo(mInfo, new Runnable() {
164 | @Override
165 | public void run() {
166 | mParent.setVisibility(View.GONE);
167 | }
168 | });
169 | }
170 | });
171 | }
172 |
173 | @Override
174 | public void onBackPressed() {
175 | if (mParent.getVisibility() == View.VISIBLE) {
176 | mBg.startAnimation(out);
177 | mPhotoView.animaTo(mInfo, new Runnable() {
178 | @Override
179 | public void run() {
180 | mParent.setVisibility(View.GONE);
181 | }
182 | });
183 | } else {
184 | super.onBackPressed();
185 | }
186 | }
187 | }
188 | ```
189 |
190 |
--------------------------------------------------------------------------------
/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/open-android/PhotoImageView/575558dc1aa8c21f5cb96dc16edfe041eb2defb5/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.os.Bundle;
5 | import android.view.View;
6 | import android.widget.ImageView;
7 |
8 | import com.itheima.library.Info;
9 | import com.itheima.library.PhotoView;
10 |
11 | public class ImageViewActivity extends Activity {
12 |
13 | ImageView img;
14 | PhotoView photoView;
15 |
16 | Info mInfo;
17 |
18 | @Override
19 | protected void onCreate(Bundle savedInstanceState) {
20 | super.onCreate(savedInstanceState);
21 | setContentView(R.layout.activity_image_view);
22 |
23 | img = (ImageView) findViewById(R.id.img);
24 | photoView = (PhotoView) findViewById(R.id.photoview);
25 | photoView.enable();
26 |
27 | img.setOnClickListener(new View.OnClickListener() {
28 | @Override
29 | public void onClick(View v) {
30 | mInfo = PhotoView.getImageViewInfo(img);
31 | img.setVisibility(View.GONE);
32 | photoView.setVisibility(View.VISIBLE);
33 | photoView.animaFrom(mInfo);
34 | }
35 | });
36 |
37 | photoView.setOnClickListener(new View.OnClickListener() {
38 | @Override
39 | public void onClick(View v) {
40 | photoView.animaTo(mInfo, new Runnable() {
41 | @Override
42 | public void run() {
43 | photoView.setVisibility(View.GONE);
44 | img.setVisibility(View.VISIBLE);
45 | }
46 | });
47 | }
48 | });
49 | }
50 |
51 |
52 |
53 |
54 |
55 | @Override
56 | public void onBackPressed() {
57 | if (photoView.getVisibility() == View.VISIBLE) {
58 | photoView.animaTo(mInfo, new Runnable() {
59 | @Override
60 | public void run() {
61 | photoView.setVisibility(View.GONE);
62 | img.setVisibility(View.VISIBLE);
63 | }
64 | });
65 | } else {
66 | super.onBackPressed();
67 | }
68 | }
69 | }
--------------------------------------------------------------------------------
/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.itheima.library.PhotoView;
9 |
10 |
11 | public class ImgActivity extends Activity {
12 |
13 | String url = "https://ss0.baidu.com/94o3dSag_xI4khGko9WTAnF6hhy/super/whfpf%3D425%2C260%2C50/sign=30f49b810ff79052ef4a147e6acee3f8/5bafa40f4bfbfbedbb34deed7ef0f736afc31f36.jpg";
14 | String gif = "http://imgsrc.baidu.com/baike/pic/item/7af40ad162d9f2d339d2a789abec8a136227cc91.jpg";
15 |
16 | PhotoView mPhotoView;
17 |
18 | @Override
19 | protected void onCreate(Bundle savedInstanceState) {
20 | super.onCreate(savedInstanceState);
21 | setContentView(R.layout.activity_img);
22 |
23 | mPhotoView = (PhotoView) findViewById(R.id.img1);
24 | mPhotoView.enable();
25 |
26 | mPhotoView.setOnLongClickListener(new View.OnLongClickListener() {
27 | @Override
28 | public boolean onLongClick(View v) {
29 | Toast.makeText(ImgActivity.this, "长按了", Toast.LENGTH_SHORT).show();
30 | return false;
31 | }
32 | });
33 |
34 | // 使用ImageLoader
35 | // ImaggeLoaderConfiguration configuration = ImageLoaderConfiguration
36 | // .createDefault(this);
37 | // ImageLoader.getInstance().init(configuration);
38 | // ImageLoader.etInstance().displayImage(url, (ImageView) findViewById(R.id.img1));
39 |
40 | // 使用Glide加载的gif图片同样支持缩放功能
41 | // Glide.with(this)
42 | // .load(gif)
43 | // .crossFade()
44 | // .placeholder(R.mipmap.bbb)
45 | // .into(((PhotoView) findViewById(R.id.img1)));
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/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.widget.ImageView;
7 | import android.widget.RadioGroup;
8 |
9 | import com.itheima.library.Info;
10 | import com.itheima.library.PhotoView;
11 |
12 |
13 | public class ImgClick extends Activity implements RadioGroup.OnCheckedChangeListener {
14 |
15 | Info mRectF;
16 |
17 | PhotoView mImg1;
18 | PhotoView mImg2;
19 |
20 | @Override
21 | protected void onCreate(Bundle savedInstanceState) {
22 | super.onCreate(savedInstanceState);
23 | // getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
24 | setContentView(R.layout.activity_img_click);
25 |
26 | ((RadioGroup) findViewById(R.id.group)).setOnCheckedChangeListener(this);
27 |
28 | mImg1 = (PhotoView) findViewById(R.id.img1);
29 | mImg2 = (PhotoView) findViewById(R.id.img2);
30 |
31 | //设置不可以双指缩放移动放大等操作,跟普通的image一模一样,默认情况下就是disenable()状态
32 | mImg1.disenable();
33 | mImg1.setOnClickListener(new View.OnClickListener() {
34 | @Override
35 | public void onClick(View v) {
36 | mImg1.setVisibility(View.GONE);
37 | mImg2.setVisibility(View.VISIBLE);
38 |
39 | //获取img1的信息
40 | mRectF = mImg1.getInfo();
41 | //让img2从img1的位置变换到他本身的位置
42 | mImg2.animaFrom(mRectF);
43 | }
44 | });
45 |
46 | // 需要启动缩放需要手动开启
47 | mImg2.enable();
48 | mImg2.setOnClickListener(new View.OnClickListener() {
49 | @Override
50 | public void onClick(View v) {
51 | // 让img2从自身位置变换到原来img1图片的位置大小
52 | mImg2.animaTo(mRectF, new Runnable() {
53 | @Override
54 | public void run() {
55 | mImg2.setVisibility(View.GONE);
56 | mImg1.setVisibility(View.VISIBLE);
57 | }
58 | });
59 | }
60 | });
61 | }
62 |
63 | @Override
64 | public void onBackPressed() {
65 | if (mImg2.getVisibility() == View.VISIBLE) {
66 | mImg2.animaTo(mRectF, new Runnable() {
67 | @Override
68 | public void run() {
69 | mImg2.setVisibility(View.GONE);
70 | mImg1.setVisibility(View.VISIBLE);
71 | }
72 | });
73 | } else {
74 | super.onBackPressed();
75 | }
76 | }
77 |
78 | @Override
79 | public void onCheckedChanged(RadioGroup group, int checkedId) {
80 |
81 | switch (checkedId) {
82 | case R.id.center:
83 | mImg1.setScaleType(ImageView.ScaleType.CENTER);
84 | break;
85 | case R.id.center_crop:
86 | mImg1.setScaleType(ImageView.ScaleType.CENTER_CROP);
87 | break;
88 | case R.id.center_inside:
89 | mImg1.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
90 | break;
91 | case R.id.fit_center:
92 | mImg1.setScaleType(ImageView.ScaleType.FIT_CENTER);
93 | break;
94 |
95 | // 建议用了fit_Xy,fit_end,fit_start就不要使用缩放或者animaFrom或animaTo
96 | case R.id.fit_end:
97 | mImg1.setScaleType(ImageView.ScaleType.FIT_END);
98 | break;
99 | case R.id.fit_start:
100 | mImg1.setScaleType(ImageView.ScaleType.FIT_START);
101 | break;
102 | case R.id.fit_xy:
103 | mImg1.setScaleType(ImageView.ScaleType.FIT_XY);
104 | break;
105 | }
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/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 | public class MainActivity extends Activity {
13 |
14 | @Override
15 | protected void onCreate(Bundle savedInstanceState) {
16 | super.onCreate(savedInstanceState);
17 | setContentView(R.layout.activity_main);
18 |
19 |
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.itheima.library.Info;
17 | import com.itheima.library.PhotoView;
18 |
19 |
20 | public class PhotoBrowse extends Activity {
21 |
22 | int[] imgs = new int[]{R.mipmap.aaa, R.mipmap.bbb, R.mipmap.ccc, R.mipmap.ddd, R.mipmap.ic_launcher, R.mipmap.image003};
23 |
24 | GridView gv;
25 |
26 | View mParent;
27 | View mBg;
28 | PhotoView mPhotoView;
29 | Info mInfo;
30 |
31 | AlphaAnimation in = new AlphaAnimation(0, 1);
32 | AlphaAnimation out = new AlphaAnimation(1, 0);
33 |
34 | @Override
35 | protected void onCreate(Bundle savedInstanceState) {
36 | super.onCreate(savedInstanceState);
37 | requestWindowFeature(Window.FEATURE_NO_TITLE);
38 | setContentView(R.layout.activity_photo_browse);
39 |
40 | in.setDuration(300);
41 | out.setDuration(300);
42 | out.setAnimationListener(new Animation.AnimationListener() {
43 | @Override
44 | public void onAnimationStart(Animation animation) {
45 |
46 | }
47 |
48 | @Override
49 | public void onAnimationEnd(Animation animation) {
50 | mBg.setVisibility(View.INVISIBLE);
51 | }
52 |
53 | @Override
54 | public void onAnimationRepeat(Animation animation) {
55 |
56 | }
57 | });
58 |
59 | mParent = findViewById(R.id.parent);
60 | mBg = findViewById(R.id.bg);
61 | mPhotoView = (PhotoView) findViewById(R.id.img);
62 | mPhotoView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
63 |
64 | gv = (GridView) findViewById(R.id.gv);
65 | gv.setAdapter(new BaseAdapter() {
66 | @Override
67 | public int getCount() {
68 | return imgs.length;
69 | }
70 |
71 | @Override
72 | public Object getItem(int position) {
73 | return null;
74 | }
75 |
76 | @Override
77 | public long getItemId(int position) {
78 | return 0;
79 | }
80 |
81 | @Override
82 | public View getView(int position, View convertView, ViewGroup parent) {
83 | PhotoView p = new PhotoView(PhotoBrowse.this);
84 | p.setLayoutParams(new AbsListView.LayoutParams((int) (getResources().getDisplayMetrics().density * 100), (int) (getResources().getDisplayMetrics().density * 100)));
85 | p.setScaleType(ImageView.ScaleType.CENTER_CROP);
86 | p.setImageResource(imgs[position]);
87 | // 把PhotoView当普通的控件把触摸功能关掉
88 | p.disenable();
89 | return p;
90 | }
91 | });
92 |
93 | gv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
94 | @Override
95 | public void onItemClick(AdapterView> parent, View view, int position, long id) {
96 | PhotoView p = (PhotoView) view;
97 | mInfo = p.getInfo();
98 |
99 | mPhotoView.setImageResource(imgs[position]);
100 | mBg.startAnimation(in);
101 | mBg.setVisibility(View.VISIBLE);
102 | mParent.setVisibility(View.VISIBLE);;
103 | mPhotoView.animaFrom(mInfo);
104 | }
105 | });
106 |
107 | mPhotoView.enable();
108 | mPhotoView.setOnClickListener(new View.OnClickListener() {
109 | @Override
110 | public void onClick(View v) {
111 | mBg.startAnimation(out);
112 | mPhotoView.animaTo(mInfo, new Runnable() {
113 | @Override
114 | public void run() {
115 | mParent.setVisibility(View.GONE);
116 | }
117 | });
118 | }
119 | });
120 | }
121 |
122 | @Override
123 | public void onBackPressed() {
124 | if (mParent.getVisibility() == View.VISIBLE) {
125 | mBg.startAnimation(out);
126 | mPhotoView.animaTo(mInfo, new Runnable() {
127 | @Override
128 | public void run() {
129 | mParent.setVisibility(View.GONE);
130 | }
131 | });
132 | } else {
133 | super.onBackPressed();
134 | }
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/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.itheima.library.PhotoView;
12 |
13 |
14 | public class ViewPagerActivity extends Activity {
15 |
16 | private ViewPager mPager;
17 |
18 | private int[] imgsId = new int[]{R.mipmap.aaa, R.mipmap.bbb, R.mipmap.ccc, R.mipmap.ddd, R.mipmap.ic_launcher, R.mipmap.image003};
19 |
20 | @Override
21 | protected void onCreate(Bundle savedInstanceState) {
22 | super.onCreate(savedInstanceState);
23 | setContentView(R.layout.activity_view_pager);
24 |
25 | mPager = (ViewPager) findViewById(R.id.pager);
26 | mPager.setPageMargin((int) (getResources().getDisplayMetrics().density * 15));
27 | mPager.setAdapter(new PagerAdapter() {
28 | @Override
29 | public int getCount() {
30 | return imgsId.length;
31 | }
32 |
33 | @Override
34 | public boolean isViewFromObject(View view, Object object) {
35 | return view == object;
36 | }
37 |
38 | @Override
39 | public Object instantiateItem(ViewGroup container, int position) {
40 | PhotoView view = new PhotoView(ViewPagerActivity.this);
41 | view.enable();
42 | view.setScaleType(ImageView.ScaleType.FIT_CENTER);
43 | view.setImageResource(imgsId[position]);
44 | container.addView(view);
45 | return view;
46 | }
47 |
48 | @Override
49 | public void destroyItem(ViewGroup container, int position, Object object) {
50 | container.removeView((View) object);
51 | }
52 | });
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/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/open-android/PhotoImageView/575558dc1aa8c21f5cb96dc16edfe041eb2defb5/app/src/main/res/mipmap-hdpi/aaa.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/bbb.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-android/PhotoImageView/575558dc1aa8c21f5cb96dc16edfe041eb2defb5/app/src/main/res/mipmap-hdpi/bbb.jpg
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ccc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-android/PhotoImageView/575558dc1aa8c21f5cb96dc16edfe041eb2defb5/app/src/main/res/mipmap-hdpi/ccc.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ddd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-android/PhotoImageView/575558dc1aa8c21f5cb96dc16edfe041eb2defb5/app/src/main/res/mipmap-hdpi/ddd.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-android/PhotoImageView/575558dc1aa8c21f5cb96dc16edfe041eb2defb5/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/image003.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-android/PhotoImageView/575558dc1aa8c21f5cb96dc16edfe041eb2defb5/app/src/main/res/mipmap-hdpi/image003.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-android/PhotoImageView/575558dc1aa8c21f5cb96dc16edfe041eb2defb5/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-android/PhotoImageView/575558dc1aa8c21f5cb96dc16edfe041eb2defb5/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-android/PhotoImageView/575558dc1aa8c21f5cb96dc16edfe041eb2defb5/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.2.2'
9 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' // Add this line
10 | // classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3'
11 | // classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.0'
12 | // NOTE: Do not place your application dependencies here; they belong
13 | // in the individual module build.gradle files
14 | }
15 | }
16 |
17 | allprojects {
18 | repositories {
19 | jcenter()
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/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/open-android/PhotoImageView/575558dc1aa8c21f5cb96dc16edfe041eb2defb5/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Mar 01 14:18:30 CST 2017
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.14.1-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/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'com.github.dcendents.android-maven'
3 | group='com.github.open-android'
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/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/itheima/library/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.itheima.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/itheima/library/Info.java:
--------------------------------------------------------------------------------
1 | package com.itheima.library;
2 |
3 | import android.graphics.PointF;
4 | import android.graphics.RectF;
5 | import android.widget.ImageView;
6 |
7 |
8 | public class Info {
9 |
10 | // 内部图片在整个手机界面的位置
11 | RectF mRect = new RectF();
12 |
13 | // 控件在窗口的位置
14 | RectF mImgRect = new RectF();
15 |
16 | RectF mWidgetRect = new RectF();
17 |
18 | RectF mBaseRect = new RectF();
19 |
20 | PointF mScreenCenter = new PointF();
21 |
22 | float mScale;
23 |
24 | float mDegrees;
25 |
26 | ImageView.ScaleType mScaleType;
27 |
28 | public Info(RectF rect, RectF img, RectF widget, RectF base, PointF screenCenter, float scale, float degrees, ImageView.ScaleType scaleType) {
29 | mRect.set(rect);
30 | mImgRect.set(img);
31 | mWidgetRect.set(widget);
32 | mScale = scale;
33 | mScaleType = scaleType;
34 | mDegrees = degrees;
35 | mBaseRect.set(base);
36 | mScreenCenter.set(screenCenter);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/library/src/main/java/com/itheima/library/PhotoView.java:
--------------------------------------------------------------------------------
1 | package com.itheima.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 | public class PhotoView extends ImageView {
24 |
25 | private final static int MIN_ROTATE = 35;
26 | private final static int ANIMA_DURING = 340;
27 | private final static float MAX_SCALE = 2.5f;
28 |
29 | private int mMinRotate;
30 | private int mAnimaDuring;
31 | private float mMaxScale;
32 |
33 | private int MAX_OVER_SCROLL = 0;
34 | private int MAX_FLING_OVER_SCROLL = 0;
35 | private int MAX_OVER_RESISTANCE = 0;
36 | private int MAX_ANIM_FROM_WAITE = 500;
37 |
38 | private Matrix mBaseMatrix = new Matrix();
39 | private Matrix mAnimaMatrix = new Matrix();
40 | private Matrix mSynthesisMatrix = new Matrix();
41 | private Matrix mTmpMatrix = new Matrix();
42 |
43 | private RotateGestureDetector mRotateDetector;
44 | private GestureDetector mDetector;
45 | private ScaleGestureDetector mScaleDetector;
46 | private OnClickListener mClickListener;
47 |
48 | private ScaleType mScaleType;
49 |
50 | private boolean hasMultiTouch;
51 | private boolean hasDrawable;
52 | private boolean isKnowSize;
53 | private boolean hasOverTranslate;
54 | private boolean isEnable = false;
55 | private boolean isRotateEnable = false;
56 | private boolean isInit;
57 | private boolean mAdjustViewBounds;
58 | // 当前是否处于放大状态
59 | private boolean isZoonUp;
60 | private boolean canRotate;
61 |
62 | private boolean imgLargeWidth;
63 | private boolean imgLargeHeight;
64 |
65 | private float mRotateFlag;
66 | private float mDegrees;
67 | private float mScale = 1.0f;
68 | private int mTranslateX;
69 | private int mTranslateY;
70 |
71 | private float mHalfBaseRectWidth;
72 | private float mHalfBaseRectHeight;
73 |
74 | private RectF mWidgetRect = new RectF();
75 | private RectF mBaseRect = new RectF();
76 | private RectF mImgRect = new RectF();
77 | private RectF mTmpRect = new RectF();
78 | private RectF mCommonRect = new RectF();
79 |
80 | private PointF mScreenCenter = new PointF();
81 | private PointF mScaleCenter = new PointF();
82 | private PointF mRotateCenter = new PointF();
83 |
84 | private Transform mTranslate = new Transform();
85 |
86 | private RectF mClip;
87 | private Info mFromInfo;
88 | private long mInfoTime;
89 | private Runnable mCompleteCallBack;
90 |
91 | private OnLongClickListener mLongClick;
92 |
93 | public PhotoView(Context context) {
94 | super(context);
95 | init();
96 | }
97 |
98 | public PhotoView(Context context, AttributeSet attrs) {
99 | super(context, attrs);
100 | init();
101 | }
102 |
103 | public PhotoView(Context context, AttributeSet attrs, int defStyleAttr) {
104 | super(context, attrs, defStyleAttr);
105 | init();
106 | }
107 |
108 | private void init() {
109 | super.setScaleType(ScaleType.MATRIX);
110 | if (mScaleType == null) mScaleType = ScaleType.CENTER_INSIDE;
111 | mRotateDetector = new RotateGestureDetector(mRotateListener);
112 | mDetector = new GestureDetector(getContext(), mGestureListener);
113 | mScaleDetector = new ScaleGestureDetector(getContext(), mScaleListener);
114 | float density = getResources().getDisplayMetrics().density;
115 | MAX_OVER_SCROLL = (int) (density * 30);
116 | MAX_FLING_OVER_SCROLL = (int) (density * 30);
117 | MAX_OVER_RESISTANCE = (int) (density * 140);
118 |
119 | mMinRotate = MIN_ROTATE;
120 | mAnimaDuring = ANIMA_DURING;
121 | mMaxScale = MAX_SCALE;
122 | }
123 |
124 | /**
125 | * 获取默认的动画持续时间
126 | */
127 | public int getDefaultAnimaDuring() {
128 | return ANIMA_DURING;
129 | }
130 |
131 | @Override
132 | public void setOnClickListener(OnClickListener l) {
133 | super.setOnClickListener(l);
134 | mClickListener = l;
135 | }
136 |
137 | @Override
138 | public void setScaleType(ScaleType scaleType) {
139 | if (scaleType == ScaleType.MATRIX) return;
140 |
141 | if (scaleType != mScaleType) {
142 | mScaleType = scaleType;
143 |
144 | if (isInit) {
145 | initBase();
146 | }
147 | }
148 | }
149 |
150 | @Override
151 | public void setOnLongClickListener(OnLongClickListener l) {
152 | mLongClick = l;
153 | }
154 |
155 | /**
156 | * 设置动画的插入器
157 | */
158 | public void setInterpolator(Interpolator interpolator) {
159 | mTranslate.setInterpolator(interpolator);
160 | }
161 |
162 | /**
163 | * 获取动画持续时间
164 | */
165 | public int getAnimaDuring() {
166 | return mAnimaDuring;
167 | }
168 |
169 | /**
170 | * 设置动画的持续时间
171 | */
172 | public void setAnimaDuring(int during) {
173 | mAnimaDuring = during;
174 | }
175 |
176 | /**
177 | * 设置最大可以缩放的倍数
178 | */
179 | public void setMaxScale(float maxScale) {
180 | mMaxScale = maxScale;
181 | }
182 |
183 | /**
184 | * 获取最大可以缩放的倍数
185 | */
186 | public float getMaxScale() {
187 | return mMaxScale;
188 | }
189 |
190 | /**
191 | * 启用缩放功能
192 | */
193 | public void enable() {
194 | isEnable = true;
195 | }
196 |
197 | /**
198 | * 禁用缩放功能
199 | */
200 | public void disenable() {
201 | isEnable = false;
202 | }
203 |
204 | /**
205 | * 启用旋转功能
206 | */
207 | public void enableRotate() {
208 | isRotateEnable = true;
209 | }
210 |
211 | /**
212 | * 禁用旋转功能
213 | */
214 | public void disableRotate() {
215 | isRotateEnable = false;
216 | }
217 |
218 | /**
219 | */
220 | public void setMaxAnimFromWaiteTime(int wait) {
221 | MAX_ANIM_FROM_WAITE = wait;
222 | }
223 |
224 | @Override
225 | public void setImageResource(int resId) {
226 | Drawable drawable = null;
227 | try {
228 | drawable = getResources().getDrawable(resId);
229 | } catch (Exception e) {
230 | }
231 |
232 | setImageDrawable(drawable);
233 | }
234 |
235 | @Override
236 | public void setImageDrawable(Drawable drawable) {
237 | super.setImageDrawable(drawable);
238 |
239 | if (drawable == null) {
240 | hasDrawable = false;
241 | return;
242 | }
243 |
244 | if (!hasSize(drawable))
245 | return;
246 |
247 | if (!hasDrawable) {
248 | hasDrawable = true;
249 | }
250 |
251 | initBase();
252 | }
253 |
254 | private boolean hasSize(Drawable d) {
255 | if ((d.getIntrinsicHeight() <= 0 || d.getIntrinsicWidth() <= 0)
256 | && (d.getMinimumWidth() <= 0 || d.getMinimumHeight() <= 0)
257 | && (d.getBounds().width() <= 0 || d.getBounds().height() <= 0)) {
258 | return false;
259 | }
260 | return true;
261 | }
262 |
263 | private static int getDrawableWidth(Drawable d) {
264 | int width = d.getIntrinsicWidth();
265 | if (width <= 0) width = d.getMinimumWidth();
266 | if (width <= 0) width = d.getBounds().width();
267 | return width;
268 | }
269 |
270 | private static int getDrawableHeight(Drawable d) {
271 | int height = d.getIntrinsicHeight();
272 | if (height <= 0) height = d.getMinimumHeight();
273 | if (height <= 0) height = d.getBounds().height();
274 | return height;
275 | }
276 |
277 | private void initBase() {
278 | if (!hasDrawable) return;
279 | if (!isKnowSize) return;
280 |
281 | mBaseMatrix.reset();
282 | mAnimaMatrix.reset();
283 |
284 | isZoonUp = false;
285 |
286 | Drawable img = getDrawable();
287 |
288 | int w = getWidth();
289 | int h = getHeight();
290 | int imgw = getDrawableWidth(img);
291 | int imgh = getDrawableHeight(img);
292 |
293 | mBaseRect.set(0, 0, imgw, imgh);
294 |
295 | // 以图片中心点居中位移
296 | int tx = (w - imgw) / 2;
297 | int ty = (h - imgh) / 2;
298 |
299 | float sx = 1;
300 | float sy = 1;
301 |
302 | // 缩放,默认不超过屏幕大小
303 | if (imgw > w) {
304 | sx = (float) w / imgw;
305 | }
306 |
307 | if (imgh > h) {
308 | sy = (float) h / imgh;
309 | }
310 |
311 | float scale = sx < sy ? sx : sy;
312 |
313 | mBaseMatrix.reset();
314 | mBaseMatrix.postTranslate(tx, ty);
315 | mBaseMatrix.postScale(scale, scale, mScreenCenter.x, mScreenCenter.y);
316 | mBaseMatrix.mapRect(mBaseRect);
317 |
318 | mHalfBaseRectWidth = mBaseRect.width() / 2;
319 | mHalfBaseRectHeight = mBaseRect.height() / 2;
320 |
321 | mScaleCenter.set(mScreenCenter);
322 | mRotateCenter.set(mScaleCenter);
323 |
324 | executeTranslate();
325 |
326 | switch (mScaleType) {
327 | case CENTER:
328 | initCenter();
329 | break;
330 | case CENTER_CROP:
331 | initCenterCrop();
332 | break;
333 | case CENTER_INSIDE:
334 | initCenterInside();
335 | break;
336 | case FIT_CENTER:
337 | initFitCenter();
338 | break;
339 | case FIT_START:
340 | initFitStart();
341 | break;
342 | case FIT_END:
343 | initFitEnd();
344 | break;
345 | case FIT_XY:
346 | initFitXY();
347 | break;
348 | }
349 |
350 | isInit = true;
351 |
352 | if (mFromInfo != null && System.currentTimeMillis() - mInfoTime < MAX_ANIM_FROM_WAITE) {
353 | animaFrom(mFromInfo);
354 | }
355 |
356 | mFromInfo = null;
357 | }
358 |
359 | private void initCenter() {
360 | if (!hasDrawable) return;
361 | if (!isKnowSize) return;
362 |
363 | Drawable img = getDrawable();
364 |
365 | int imgw = getDrawableWidth(img);
366 | int imgh = getDrawableHeight(img);
367 |
368 | if (imgw > mWidgetRect.width() || imgh > mWidgetRect.height()) {
369 | float scaleX = imgw / mImgRect.width();
370 | float scaleY = imgh / mImgRect.height();
371 |
372 | mScale = scaleX > scaleY ? scaleX : scaleY;
373 |
374 | mAnimaMatrix.postScale(mScale, mScale, mScreenCenter.x, mScreenCenter.y);
375 |
376 | executeTranslate();
377 |
378 | resetBase();
379 | }
380 | }
381 |
382 | private void initCenterCrop() {
383 | if (mImgRect.width() < mWidgetRect.width() || mImgRect.height() < mWidgetRect.height()) {
384 | float scaleX = mWidgetRect.width() / mImgRect.width();
385 | float scaleY = mWidgetRect.height() / mImgRect.height();
386 |
387 | mScale = scaleX > scaleY ? scaleX : scaleY;
388 |
389 | mAnimaMatrix.postScale(mScale, mScale, mScreenCenter.x, mScreenCenter.y);
390 |
391 | executeTranslate();
392 | resetBase();
393 | }
394 | }
395 |
396 | private void initCenterInside() {
397 | if (mImgRect.width() > mWidgetRect.width() || mImgRect.height() > mWidgetRect.height()) {
398 | float scaleX = mWidgetRect.width() / mImgRect.width();
399 | float scaleY = mWidgetRect.height() / mImgRect.height();
400 |
401 | mScale = scaleX < scaleY ? scaleX : scaleY;
402 |
403 | mAnimaMatrix.postScale(mScale, mScale, mScreenCenter.x, mScreenCenter.y);
404 |
405 | executeTranslate();
406 | resetBase();
407 | }
408 | }
409 |
410 | private void initFitCenter() {
411 | if (mImgRect.width() < mWidgetRect.width()) {
412 | mScale = mWidgetRect.width() / mImgRect.width();
413 |
414 | mAnimaMatrix.postScale(mScale, mScale, mScreenCenter.x, mScreenCenter.y);
415 |
416 | executeTranslate();
417 | resetBase();
418 | }
419 | }
420 |
421 | private void initFitStart() {
422 | initFitCenter();
423 |
424 | float ty = -mImgRect.top;
425 | mAnimaMatrix.postTranslate(0, ty);
426 | executeTranslate();
427 | resetBase();
428 | mTranslateY += ty;
429 | }
430 |
431 | private void initFitEnd() {
432 | initFitCenter();
433 |
434 | float ty = (mWidgetRect.bottom - mImgRect.bottom);
435 | mTranslateY += ty;
436 | mAnimaMatrix.postTranslate(0, ty);
437 | executeTranslate();
438 | resetBase();
439 | }
440 |
441 | private void initFitXY() {
442 | float scaleX = mWidgetRect.width() / mImgRect.width();
443 | float scaleY = mWidgetRect.height() / mImgRect.height();
444 |
445 | mAnimaMatrix.postScale(scaleX, scaleY, mScreenCenter.x, mScreenCenter.y);
446 |
447 | executeTranslate();
448 | resetBase();
449 | }
450 |
451 | private void resetBase() {
452 | Drawable img = getDrawable();
453 | int imgw = getDrawableWidth(img);
454 | int imgh = getDrawableHeight(img);
455 | mBaseRect.set(0, 0, imgw, imgh);
456 | mBaseMatrix.set(mSynthesisMatrix);
457 | mBaseMatrix.mapRect(mBaseRect);
458 | mHalfBaseRectWidth = mBaseRect.width() / 2;
459 | mHalfBaseRectHeight = mBaseRect.height() / 2;
460 | mScale = 1;
461 | mTranslateX = 0;
462 | mTranslateY = 0;
463 | mAnimaMatrix.reset();
464 | }
465 |
466 | private void executeTranslate() {
467 | mSynthesisMatrix.set(mBaseMatrix);
468 | mSynthesisMatrix.postConcat(mAnimaMatrix);
469 | setImageMatrix(mSynthesisMatrix);
470 |
471 | mAnimaMatrix.mapRect(mImgRect, mBaseRect);
472 |
473 | imgLargeWidth = mImgRect.width() > mWidgetRect.width();
474 | imgLargeHeight = mImgRect.height() > mWidgetRect.height();
475 | }
476 |
477 | @Override
478 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
479 | if (!hasDrawable) {
480 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
481 | return;
482 | }
483 |
484 | Drawable d = getDrawable();
485 | int drawableW = getDrawableWidth(d);
486 | int drawableH = getDrawableHeight(d);
487 |
488 | int pWidth = MeasureSpec.getSize(widthMeasureSpec);
489 | int pHeight = MeasureSpec.getSize(heightMeasureSpec);
490 |
491 | int widthMode = MeasureSpec.getMode(widthMeasureSpec);
492 | int heightMode = MeasureSpec.getMode(heightMeasureSpec);
493 |
494 | int width = 0;
495 | int height = 0;
496 |
497 | ViewGroup.LayoutParams p = getLayoutParams();
498 |
499 | if (p == null) {
500 | p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
501 | }
502 |
503 | if (p.width == ViewGroup.LayoutParams.MATCH_PARENT) {
504 | if (widthMode == MeasureSpec.UNSPECIFIED) {
505 | width = drawableW;
506 | } else {
507 | width = pWidth;
508 | }
509 | } else {
510 | if (widthMode == MeasureSpec.EXACTLY) {
511 | width = pWidth;
512 | } else if (widthMode == MeasureSpec.AT_MOST) {
513 | width = drawableW > pWidth ? pWidth : drawableW;
514 | } else {
515 | width = drawableW;
516 | }
517 | }
518 |
519 | if (p.height == ViewGroup.LayoutParams.MATCH_PARENT) {
520 | if (heightMode == MeasureSpec.UNSPECIFIED) {
521 | height = drawableH;
522 | } else {
523 | height = pHeight;
524 | }
525 | } else {
526 | if (heightMode == MeasureSpec.EXACTLY) {
527 | height = pHeight;
528 | } else if (heightMode == MeasureSpec.AT_MOST) {
529 | height = drawableH > pHeight ? pHeight : drawableH;
530 | } else {
531 | height = drawableH;
532 | }
533 | }
534 |
535 | if (mAdjustViewBounds && (float) drawableW / drawableH != (float) width / height) {
536 |
537 | float hScale = (float) height / drawableH;
538 | float wScale = (float) width / drawableW;
539 |
540 | float scale = hScale < wScale ? hScale : wScale;
541 | width = p.width == ViewGroup.LayoutParams.MATCH_PARENT ? width : (int) (drawableW * scale);
542 | height = p.height == ViewGroup.LayoutParams.MATCH_PARENT ? height : (int) (drawableH * scale);
543 | }
544 |
545 | setMeasuredDimension(width, height);
546 | }
547 |
548 | @Override
549 | public void setAdjustViewBounds(boolean adjustViewBounds) {
550 | super.setAdjustViewBounds(adjustViewBounds);
551 | mAdjustViewBounds = adjustViewBounds;
552 | }
553 |
554 | @Override
555 | protected void onSizeChanged(int w, int h, int oldw, int oldh) {
556 | super.onSizeChanged(w, h, oldw, oldh);
557 |
558 | mWidgetRect.set(0, 0, w, h);
559 | mScreenCenter.set(w / 2, h / 2);
560 |
561 | if (!isKnowSize) {
562 | isKnowSize = true;
563 | initBase();
564 | }
565 | }
566 |
567 | @Override
568 | public void draw(Canvas canvas) {
569 | if (mClip != null) {
570 | canvas.clipRect(mClip);
571 | mClip = null;
572 | }
573 | super.draw(canvas);
574 | }
575 |
576 | @Override
577 | public boolean dispatchTouchEvent(MotionEvent event) {
578 | if (isEnable) {
579 | final int Action = event.getActionMasked();
580 | if (event.getPointerCount() >= 2) hasMultiTouch = true;
581 |
582 | mDetector.onTouchEvent(event);
583 | if (isRotateEnable) {
584 | mRotateDetector.onTouchEvent(event);
585 | }
586 | mScaleDetector.onTouchEvent(event);
587 |
588 | if (Action == MotionEvent.ACTION_UP || Action == MotionEvent.ACTION_CANCEL) onUp();
589 |
590 | return true;
591 | } else {
592 | return super.dispatchTouchEvent(event);
593 | }
594 | }
595 |
596 | private void onUp() {
597 | if (mTranslate.isRuning) return;
598 |
599 | if (canRotate || mDegrees % 90 != 0) {
600 | float toDegrees = (int) (mDegrees / 90) * 90;
601 | float remainder = mDegrees % 90;
602 |
603 | if (remainder > 45)
604 | toDegrees += 90;
605 | else if (remainder < -45)
606 | toDegrees -= 90;
607 |
608 | mTranslate.withRotate((int) mDegrees, (int) toDegrees);
609 |
610 | mDegrees = toDegrees;
611 | }
612 |
613 | float scale = mScale;
614 |
615 | if (mScale < 1) {
616 | scale = 1;
617 | mTranslate.withScale(mScale, 1);
618 | } else if (mScale > mMaxScale) {
619 | scale = mMaxScale;
620 | mTranslate.withScale(mScale, mMaxScale);
621 | }
622 |
623 | float cx = mImgRect.left + mImgRect.width() / 2;
624 | float cy = mImgRect.top + mImgRect.height() / 2;
625 |
626 | mScaleCenter.set(cx, cy);
627 | mRotateCenter.set(cx, cy);
628 |
629 | mTranslateX = 0;
630 | mTranslateY = 0;
631 |
632 | mTmpMatrix.reset();
633 | mTmpMatrix.postTranslate(-mBaseRect.left, -mBaseRect.top);
634 | mTmpMatrix.postTranslate(cx - mHalfBaseRectWidth, cy - mHalfBaseRectHeight);
635 | mTmpMatrix.postScale(scale, scale, cx, cy);
636 | mTmpMatrix.postRotate(mDegrees, cx, cy);
637 | mTmpMatrix.mapRect(mTmpRect, mBaseRect);
638 |
639 | doTranslateReset(mTmpRect);
640 | mTranslate.start();
641 | }
642 |
643 | private void doTranslateReset(RectF imgRect) {
644 | int tx = 0;
645 | int ty = 0;
646 |
647 | if (imgRect.width() <= mWidgetRect.width()) {
648 | if (!isImageCenterWidth(imgRect))
649 | tx = -(int) ((mWidgetRect.width() - imgRect.width()) / 2 - imgRect.left);
650 | } else {
651 | if (imgRect.left > mWidgetRect.left) {
652 | tx = (int) (imgRect.left - mWidgetRect.left);
653 | } else if (imgRect.right < mWidgetRect.right) {
654 | tx = (int) (imgRect.right - mWidgetRect.right);
655 | }
656 | }
657 |
658 | if (imgRect.height() <= mWidgetRect.height()) {
659 | if (!isImageCenterHeight(imgRect))
660 | ty = -(int) ((mWidgetRect.height() - imgRect.height()) / 2 - imgRect.top);
661 | } else {
662 | if (imgRect.top > mWidgetRect.top) {
663 | ty = (int) (imgRect.top - mWidgetRect.top);
664 | } else if (imgRect.bottom < mWidgetRect.bottom) {
665 | ty = (int) (imgRect.bottom - mWidgetRect.bottom);
666 | }
667 | }
668 |
669 | if (tx != 0 || ty != 0) {
670 | if (!mTranslate.mFlingScroller.isFinished()) mTranslate.mFlingScroller.abortAnimation();
671 | mTranslate.withTranslate(mTranslateX, mTranslateY, -tx, -ty);
672 | }
673 | }
674 |
675 | private boolean isImageCenterHeight(RectF rect) {
676 | return Math.abs(Math.round(rect.top) - (mWidgetRect.height() - rect.height()) / 2) < 1;
677 | }
678 |
679 | private boolean isImageCenterWidth(RectF rect) {
680 | return Math.abs(Math.round(rect.left) - (mWidgetRect.width() - rect.width()) / 2) < 1;
681 | }
682 |
683 | private OnRotateListener mRotateListener = new OnRotateListener() {
684 |
685 | @Override
686 | public void onRotate(float degrees, float focusX, float focusY) {
687 | mRotateFlag += degrees;
688 | if (canRotate) {
689 | mDegrees += degrees;
690 | mAnimaMatrix.postRotate(degrees, focusX, focusY);
691 | } else {
692 | if (Math.abs(mRotateFlag) >= mMinRotate) {
693 | canRotate = true;
694 | mRotateFlag = 0;
695 | }
696 | }
697 | }
698 | };
699 |
700 | private ScaleGestureDetector.OnScaleGestureListener mScaleListener = new ScaleGestureDetector.OnScaleGestureListener() {
701 | @Override
702 | public boolean onScale(ScaleGestureDetector detector) {
703 | float scaleFactor = detector.getScaleFactor();
704 |
705 | if (Float.isNaN(scaleFactor) || Float.isInfinite(scaleFactor))
706 | return false;
707 |
708 | mScale *= scaleFactor;
709 | // mScaleCenter.set(detector.getFocusX(), detector.getFocusY());
710 | mAnimaMatrix.postScale(scaleFactor, scaleFactor, detector.getFocusX(), detector.getFocusY());
711 | executeTranslate();
712 | return true;
713 | }
714 |
715 | public boolean onScaleBegin(ScaleGestureDetector detector) {
716 | return true;
717 | }
718 |
719 | public void onScaleEnd(ScaleGestureDetector detector) {
720 |
721 | }
722 | };
723 |
724 | private float resistanceScrollByX(float overScroll, float detalX) {
725 | float s = detalX * (Math.abs(Math.abs(overScroll) - MAX_OVER_RESISTANCE) / (float) MAX_OVER_RESISTANCE);
726 | return s;
727 | }
728 |
729 | private float resistanceScrollByY(float overScroll, float detalY) {
730 | float s = detalY * (Math.abs(Math.abs(overScroll) - MAX_OVER_RESISTANCE) / (float) MAX_OVER_RESISTANCE);
731 | return s;
732 | }
733 |
734 | /**
735 | * 匹配两个Rect的共同部分输出到out,若无共同部分则输出0,0,0,0
736 | */
737 | private void mapRect(RectF r1, RectF r2, RectF out) {
738 |
739 | float l, r, t, b;
740 |
741 | l = r1.left > r2.left ? r1.left : r2.left;
742 | r = r1.right < r2.right ? r1.right : r2.right;
743 |
744 | if (l > r) {
745 | out.set(0, 0, 0, 0);
746 | return;
747 | }
748 |
749 | t = r1.top > r2.top ? r1.top : r2.top;
750 | b = r1.bottom < r2.bottom ? r1.bottom : r2.bottom;
751 |
752 | if (t > b) {
753 | out.set(0, 0, 0, 0);
754 | return;
755 | }
756 |
757 | out.set(l, t, r, b);
758 | }
759 |
760 | private void checkRect() {
761 | if (!hasOverTranslate) {
762 | mapRect(mWidgetRect, mImgRect, mCommonRect);
763 | }
764 | }
765 |
766 | private Runnable mClickRunnable = new Runnable() {
767 | @Override
768 | public void run() {
769 | if (mClickListener != null) {
770 | mClickListener.onClick(PhotoView.this);
771 | }
772 | }
773 | };
774 |
775 | private GestureDetector.OnGestureListener mGestureListener = new GestureDetector.SimpleOnGestureListener() {
776 |
777 | @Override
778 | public void onLongPress(MotionEvent e) {
779 | if (mLongClick != null) {
780 | mLongClick.onLongClick(PhotoView.this);
781 | }
782 | }
783 |
784 | @Override
785 | public boolean onDown(MotionEvent e) {
786 | hasOverTranslate = false;
787 | hasMultiTouch = false;
788 | canRotate = false;
789 | removeCallbacks(mClickRunnable);
790 | return false;
791 | }
792 |
793 | @Override
794 | public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
795 | if (hasMultiTouch) return false;
796 | if (!imgLargeWidth && !imgLargeHeight) return false;
797 | if (mTranslate.isRuning) return false;
798 |
799 | float vx = velocityX;
800 | float vy = velocityY;
801 |
802 | if (Math.round(mImgRect.left) >= mWidgetRect.left || Math.round(mImgRect.right) <= mWidgetRect.right) {
803 | vx = 0;
804 | }
805 |
806 | if (Math.round(mImgRect.top) >= mWidgetRect.top || Math.round(mImgRect.bottom) <= mWidgetRect.bottom) {
807 | vy = 0;
808 | }
809 |
810 | if (canRotate || mDegrees % 90 != 0) {
811 | float toDegrees = (int) (mDegrees / 90) * 90;
812 | float remainder = mDegrees % 90;
813 |
814 | if (remainder > 45)
815 | toDegrees += 90;
816 | else if (remainder < -45)
817 | toDegrees -= 90;
818 |
819 | mTranslate.withRotate((int) mDegrees, (int) toDegrees);
820 |
821 | mDegrees = toDegrees;
822 | }
823 |
824 | doTranslateReset(mImgRect);
825 |
826 | mTranslate.withFling(vx, vy);
827 |
828 | mTranslate.start();
829 | // onUp(e2);
830 | return super.onFling(e1, e2, velocityX, velocityY);
831 | }
832 |
833 | @Override
834 | public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
835 | if (mTranslate.isRuning) {
836 | mTranslate.stop();
837 | }
838 |
839 | if (canScrollHorizontallySelf(distanceX)) {
840 | if (distanceX < 0 && mImgRect.left - distanceX > mWidgetRect.left)
841 | distanceX = mImgRect.left;
842 | if (distanceX > 0 && mImgRect.right - distanceX < mWidgetRect.right)
843 | distanceX = mImgRect.right - mWidgetRect.right;
844 |
845 | mAnimaMatrix.postTranslate(-distanceX, 0);
846 | mTranslateX -= distanceX;
847 | } else if (imgLargeWidth || hasMultiTouch || hasOverTranslate) {
848 | checkRect();
849 | if (!hasMultiTouch) {
850 | if (distanceX < 0 && mImgRect.left - distanceX > mCommonRect.left)
851 | distanceX = resistanceScrollByX(mImgRect.left - mCommonRect.left, distanceX);
852 | if (distanceX > 0 && mImgRect.right - distanceX < mCommonRect.right)
853 | distanceX = resistanceScrollByX(mImgRect.right - mCommonRect.right, distanceX);
854 | }
855 |
856 | mTranslateX -= distanceX;
857 | mAnimaMatrix.postTranslate(-distanceX, 0);
858 | hasOverTranslate = true;
859 | }
860 |
861 | if (canScrollVerticallySelf(distanceY)) {
862 | if (distanceY < 0 && mImgRect.top - distanceY > mWidgetRect.top)
863 | distanceY = mImgRect.top;
864 | if (distanceY > 0 && mImgRect.bottom - distanceY < mWidgetRect.bottom)
865 | distanceY = mImgRect.bottom - mWidgetRect.bottom;
866 |
867 | mAnimaMatrix.postTranslate(0, -distanceY);
868 | mTranslateY -= distanceY;
869 | } else if (imgLargeHeight || hasOverTranslate || hasMultiTouch) {
870 | checkRect();
871 | if (!hasMultiTouch) {
872 | if (distanceY < 0 && mImgRect.top - distanceY > mCommonRect.top)
873 | distanceY = resistanceScrollByY(mImgRect.top - mCommonRect.top, distanceY);
874 | if (distanceY > 0 && mImgRect.bottom - distanceY < mCommonRect.bottom)
875 | distanceY = resistanceScrollByY(mImgRect.bottom - mCommonRect.bottom, distanceY);
876 | }
877 |
878 | mAnimaMatrix.postTranslate(0, -distanceY);
879 | mTranslateY -= distanceY;
880 | hasOverTranslate = true;
881 | }
882 |
883 | executeTranslate();
884 | return true;
885 | }
886 |
887 | @Override
888 | public boolean onSingleTapUp(MotionEvent e) {
889 | postDelayed(mClickRunnable, 250);
890 | return false;
891 | }
892 |
893 | @Override
894 | public boolean onDoubleTap(MotionEvent e) {
895 |
896 | mTranslate.stop();
897 |
898 | float from = 1;
899 | float to = 1;
900 |
901 | float imgcx = mImgRect.left + mImgRect.width() / 2;
902 | float imgcy = mImgRect.top + mImgRect.height() / 2;
903 |
904 | mScaleCenter.set(imgcx, imgcy);
905 | mRotateCenter.set(imgcx, imgcy);
906 | mTranslateX = 0;
907 | mTranslateY = 0;
908 |
909 | if (isZoonUp) {
910 | from = mScale;
911 | to = 1;
912 | } else {
913 | from = mScale;
914 | to = mMaxScale;
915 |
916 | mScaleCenter.set(e.getX(), e.getY());
917 | }
918 |
919 | mTmpMatrix.reset();
920 | mTmpMatrix.postTranslate(-mBaseRect.left, -mBaseRect.top);
921 | mTmpMatrix.postTranslate(mRotateCenter.x, mRotateCenter.y);
922 | mTmpMatrix.postTranslate(-mHalfBaseRectWidth, -mHalfBaseRectHeight);
923 | mTmpMatrix.postRotate(mDegrees, mRotateCenter.x, mRotateCenter.y);
924 | mTmpMatrix.postScale(to, to, mScaleCenter.x, mScaleCenter.y);
925 | mTmpMatrix.postTranslate(mTranslateX, mTranslateY);
926 | mTmpMatrix.mapRect(mTmpRect, mBaseRect);
927 | doTranslateReset(mTmpRect);
928 |
929 | isZoonUp = !isZoonUp;
930 | mTranslate.withScale(from, to);
931 | mTranslate.start();
932 |
933 | return false;
934 | }
935 | };
936 |
937 | public boolean canScrollHorizontallySelf(float direction) {
938 | if (mImgRect.width() <= mWidgetRect.width()) return false;
939 | if (direction < 0 && Math.round(mImgRect.left) - direction >= mWidgetRect.left)
940 | return false;
941 | if (direction > 0 && Math.round(mImgRect.right) - direction <= mWidgetRect.right)
942 | return false;
943 | return true;
944 | }
945 |
946 | public boolean canScrollVerticallySelf(float direction) {
947 | if (mImgRect.height() <= mWidgetRect.height()) return false;
948 | if (direction < 0 && Math.round(mImgRect.top) - direction >= mWidgetRect.top)
949 | return false;
950 | if (direction > 0 && Math.round(mImgRect.bottom) - direction <= mWidgetRect.bottom)
951 | return false;
952 | return true;
953 | }
954 |
955 | @Override
956 | public boolean canScrollHorizontally(int direction) {
957 | if (hasMultiTouch) return true;
958 | return canScrollHorizontallySelf(direction);
959 | }
960 |
961 | @Override
962 | public boolean canScrollVertically(int direction) {
963 | if (hasMultiTouch) return true;
964 | return canScrollVerticallySelf(direction);
965 | }
966 |
967 | private class InterpolatorProxy implements Interpolator {
968 |
969 | private Interpolator mTarget;
970 |
971 | private InterpolatorProxy() {
972 | mTarget = new DecelerateInterpolator();
973 | }
974 |
975 | public void setTargetInterpolator(Interpolator interpolator) {
976 | mTarget = interpolator;
977 | }
978 |
979 | @Override
980 | public float getInterpolation(float input) {
981 | if (mTarget != null) {
982 | return mTarget.getInterpolation(input);
983 | }
984 | return input;
985 | }
986 | }
987 |
988 | private class Transform implements Runnable {
989 |
990 | boolean isRuning;
991 |
992 | OverScroller mTranslateScroller;
993 | OverScroller mFlingScroller;
994 | Scroller mScaleScroller;
995 | Scroller mClipScroller;
996 | Scroller mRotateScroller;
997 |
998 | ClipCalculate C;
999 |
1000 | int mLastFlingX;
1001 | int mLastFlingY;
1002 |
1003 | int mLastTranslateX;
1004 | int mLastTranslateY;
1005 |
1006 | RectF mClipRect = new RectF();
1007 |
1008 | InterpolatorProxy mInterpolatorProxy = new InterpolatorProxy();
1009 |
1010 | Transform() {
1011 | Context ctx = getContext();
1012 | mTranslateScroller = new OverScroller(ctx, mInterpolatorProxy);
1013 | mScaleScroller = new Scroller(ctx, mInterpolatorProxy);
1014 | mFlingScroller = new OverScroller(ctx, mInterpolatorProxy);
1015 | mClipScroller = new Scroller(ctx, mInterpolatorProxy);
1016 | mRotateScroller = new Scroller(ctx, mInterpolatorProxy);
1017 | }
1018 |
1019 | public void setInterpolator(Interpolator interpolator) {
1020 | mInterpolatorProxy.setTargetInterpolator(interpolator);
1021 | }
1022 |
1023 | void withTranslate(int startX, int startY, int deltaX, int deltaY) {
1024 | mLastTranslateX = 0;
1025 | mLastTranslateY = 0;
1026 | mTranslateScroller.startScroll(0, 0, deltaX, deltaY, mAnimaDuring);
1027 | }
1028 |
1029 | void withScale(float form, float to) {
1030 | mScaleScroller.startScroll((int) (form * 10000), 0, (int) ((to - form) * 10000), 0, mAnimaDuring);
1031 | }
1032 |
1033 | void withClip(float fromX, float fromY, float deltaX, float deltaY, int d, ClipCalculate c) {
1034 | mClipScroller.startScroll((int) (fromX * 10000), (int) (fromY * 10000), (int) (deltaX * 10000), (int) (deltaY * 10000), d);
1035 | C = c;
1036 | }
1037 |
1038 | void withRotate(int fromDegrees, int toDegrees) {
1039 | mRotateScroller.startScroll(fromDegrees, 0, toDegrees - fromDegrees, 0, mAnimaDuring);
1040 | }
1041 |
1042 | void withRotate(int fromDegrees, int toDegrees, int during) {
1043 | mRotateScroller.startScroll(fromDegrees, 0, toDegrees - fromDegrees, 0, during);
1044 | }
1045 |
1046 | void withFling(float velocityX, float velocityY) {
1047 | mLastFlingX = velocityX < 0 ? Integer.MAX_VALUE : 0;
1048 | int distanceX = (int) (velocityX > 0 ? Math.abs(mImgRect.left) : mImgRect.right - mWidgetRect.right);
1049 | distanceX = velocityX < 0 ? Integer.MAX_VALUE - distanceX : distanceX;
1050 | int minX = velocityX < 0 ? distanceX : 0;
1051 | int maxX = velocityX < 0 ? Integer.MAX_VALUE : distanceX;
1052 | int overX = velocityX < 0 ? Integer.MAX_VALUE - minX : distanceX;
1053 |
1054 | mLastFlingY = velocityY < 0 ? Integer.MAX_VALUE : 0;
1055 | int distanceY = (int) (velocityY > 0 ? Math.abs(mImgRect.top) : mImgRect.bottom - mWidgetRect.bottom);
1056 | distanceY = velocityY < 0 ? Integer.MAX_VALUE - distanceY : distanceY;
1057 | int minY = velocityY < 0 ? distanceY : 0;
1058 | int maxY = velocityY < 0 ? Integer.MAX_VALUE : distanceY;
1059 | int overY = velocityY < 0 ? Integer.MAX_VALUE - minY : distanceY;
1060 |
1061 | if (velocityX == 0) {
1062 | maxX = 0;
1063 | minX = 0;
1064 | }
1065 |
1066 | if (velocityY == 0) {
1067 | maxY = 0;
1068 | minY = 0;
1069 | }
1070 |
1071 | 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);
1072 | }
1073 |
1074 | void start() {
1075 | isRuning = true;
1076 | postExecute();
1077 | }
1078 |
1079 | void stop() {
1080 | removeCallbacks(this);
1081 | mTranslateScroller.abortAnimation();
1082 | mScaleScroller.abortAnimation();
1083 | mFlingScroller.abortAnimation();
1084 | mRotateScroller.abortAnimation();
1085 | isRuning = false;
1086 | }
1087 |
1088 | @Override
1089 | public void run() {
1090 |
1091 | // if (!isRuning) return;
1092 |
1093 | boolean endAnima = true;
1094 |
1095 | if (mScaleScroller.computeScrollOffset()) {
1096 | mScale = mScaleScroller.getCurrX() / 10000f;
1097 | endAnima = false;
1098 | }
1099 |
1100 | if (mTranslateScroller.computeScrollOffset()) {
1101 | int tx = mTranslateScroller.getCurrX() - mLastTranslateX;
1102 | int ty = mTranslateScroller.getCurrY() - mLastTranslateY;
1103 | mTranslateX += tx;
1104 | mTranslateY += ty;
1105 | mLastTranslateX = mTranslateScroller.getCurrX();
1106 | mLastTranslateY = mTranslateScroller.getCurrY();
1107 | endAnima = false;
1108 | }
1109 |
1110 | if (mFlingScroller.computeScrollOffset()) {
1111 | int x = mFlingScroller.getCurrX() - mLastFlingX;
1112 | int y = mFlingScroller.getCurrY() - mLastFlingY;
1113 |
1114 | mLastFlingX = mFlingScroller.getCurrX();
1115 | mLastFlingY = mFlingScroller.getCurrY();
1116 |
1117 | mTranslateX += x;
1118 | mTranslateY += y;
1119 | endAnima = false;
1120 | }
1121 |
1122 | if (mRotateScroller.computeScrollOffset()) {
1123 | mDegrees = mRotateScroller.getCurrX();
1124 | endAnima = false;
1125 | }
1126 |
1127 | if (mClipScroller.computeScrollOffset() || mClip != null) {
1128 | float sx = mClipScroller.getCurrX() / 10000f;
1129 | float sy = mClipScroller.getCurrY() / 10000f;
1130 | mTmpMatrix.setScale(sx, sy, (mImgRect.left + mImgRect.right) / 2, C.calculateTop());
1131 | mTmpMatrix.mapRect(mClipRect, mImgRect);
1132 |
1133 | if (sx == 1) {
1134 | mClipRect.left = mWidgetRect.left;
1135 | mClipRect.right = mWidgetRect.right;
1136 | }
1137 |
1138 | if (sy == 1) {
1139 | mClipRect.top = mWidgetRect.top;
1140 | mClipRect.bottom = mWidgetRect.bottom;
1141 | }
1142 |
1143 | mClip = mClipRect;
1144 | }
1145 |
1146 | if (!endAnima) {
1147 | applyAnima();
1148 | postExecute();
1149 | } else {
1150 | isRuning = false;
1151 |
1152 | // 修复动画结束后边距有些空隙,
1153 | boolean needFix = false;
1154 |
1155 | if (imgLargeWidth) {
1156 | if (mImgRect.left > 0) {
1157 | mTranslateX -= mImgRect.left;
1158 | } else if (mImgRect.right < mWidgetRect.width()) {
1159 | mTranslateX -= (int) (mWidgetRect.width() - mImgRect.right);
1160 | }
1161 | needFix = true;
1162 | }
1163 |
1164 | if (imgLargeHeight) {
1165 | if (mImgRect.top > 0) {
1166 | mTranslateY -= mImgRect.top;
1167 | } else if (mImgRect.bottom < mWidgetRect.height()) {
1168 | mTranslateY -= (int) (mWidgetRect.height() - mImgRect.bottom);
1169 | }
1170 | needFix = true;
1171 | }
1172 |
1173 | if (needFix) {
1174 | applyAnima();
1175 | }
1176 |
1177 | invalidate();
1178 |
1179 | if (mCompleteCallBack != null) {
1180 | mCompleteCallBack.run();
1181 | mCompleteCallBack = null;
1182 | }
1183 | }
1184 | }
1185 |
1186 | private void applyAnima() {
1187 | mAnimaMatrix.reset();
1188 | mAnimaMatrix.postTranslate(-mBaseRect.left, -mBaseRect.top);
1189 | mAnimaMatrix.postTranslate(mRotateCenter.x, mRotateCenter.y);
1190 | mAnimaMatrix.postTranslate(-mHalfBaseRectWidth, -mHalfBaseRectHeight);
1191 | mAnimaMatrix.postRotate(mDegrees, mRotateCenter.x, mRotateCenter.y);
1192 | mAnimaMatrix.postScale(mScale, mScale, mScaleCenter.x, mScaleCenter.y);
1193 | mAnimaMatrix.postTranslate(mTranslateX, mTranslateY);
1194 | executeTranslate();
1195 | }
1196 |
1197 |
1198 | private void postExecute() {
1199 | if (isRuning) post(this);
1200 | }
1201 | }
1202 |
1203 | public Info getInfo() {
1204 | RectF rect = new RectF();
1205 | int[] p = new int[2];
1206 | getLocation(this, p);
1207 | rect.set(p[0] + mImgRect.left, p[1] + mImgRect.top, p[0] + mImgRect.right, p[1] + mImgRect.bottom);
1208 | return new Info(rect, mImgRect, mWidgetRect, mBaseRect, mScreenCenter, mScale, mDegrees, mScaleType);
1209 | }
1210 |
1211 | public static Info getImageViewInfo(ImageView imgView) {
1212 | int[] p = new int[2];
1213 | getLocation(imgView, p);
1214 |
1215 | Drawable drawable = imgView.getDrawable();
1216 |
1217 | Matrix matrix = imgView.getImageMatrix();
1218 |
1219 | int width = getDrawableWidth(drawable);
1220 | int height = getDrawableHeight(drawable);
1221 |
1222 | RectF imgRect = new RectF(0, 0, width, height);
1223 | matrix.mapRect(imgRect);
1224 |
1225 | RectF rect = new RectF(p[0] + imgRect.left, p[1] + imgRect.top, p[0] + imgRect.right, p[1] + imgRect.bottom);
1226 | RectF widgetRect = new RectF(0, 0, imgView.getWidth(), imgView.getHeight());
1227 | RectF baseRect = new RectF(widgetRect);
1228 | PointF screenCenter = new PointF(widgetRect.width() / 2, widgetRect.height() / 2);
1229 |
1230 | return new Info(rect, imgRect, widgetRect, baseRect, screenCenter, 1, 0, imgView.getScaleType());
1231 | }
1232 |
1233 | private static void getLocation(View target, int[] position) {
1234 |
1235 | position[0] += target.getLeft();
1236 | position[1] += target.getTop();
1237 |
1238 | ViewParent viewParent = target.getParent();
1239 | while (viewParent instanceof View) {
1240 | final View view = (View) viewParent;
1241 |
1242 | if (view.getId() == android.R.id.content) return;
1243 |
1244 | position[0] -= view.getScrollX();
1245 | position[1] -= view.getScrollY();
1246 |
1247 | position[0] += view.getLeft();
1248 | position[1] += view.getTop();
1249 |
1250 | viewParent = view.getParent();
1251 | }
1252 |
1253 | position[0] = (int) (position[0] + 0.5f);
1254 | position[1] = (int) (position[1] + 0.5f);
1255 | }
1256 |
1257 | private void reset() {
1258 | mAnimaMatrix.reset();
1259 | executeTranslate();
1260 | mScale = 1;
1261 | mTranslateX = 0;
1262 | mTranslateY = 0;
1263 | }
1264 |
1265 | public interface ClipCalculate {
1266 | float calculateTop();
1267 | }
1268 |
1269 | public class START implements ClipCalculate {
1270 | public float calculateTop() {
1271 | return mImgRect.top;
1272 | }
1273 | }
1274 |
1275 | public class END implements ClipCalculate {
1276 | public float calculateTop() {
1277 | return mImgRect.bottom;
1278 | }
1279 | }
1280 |
1281 | public class OTHER implements ClipCalculate {
1282 | public float calculateTop() {
1283 | return (mImgRect.top + mImgRect.bottom) / 2;
1284 | }
1285 | }
1286 |
1287 | /**
1288 | * 在PhotoView内部还没有图片的时候同样可以调用该方法
1289 | *
1290 | * 此时并不会播放动画,当给PhotoView设置图片后会自动播放动画。
1291 | *
1292 | * 若等待时间过长也没有给控件设置图片,则会忽略该动画,若要再次播放动画则需要重新调用该方法
1293 | * (等待的时间默认500毫秒,可以通过setMaxAnimFromWaiteTime(int)设置最大等待时间)
1294 | */
1295 | public void animaFrom(Info info) {
1296 | if (isInit) {
1297 | reset();
1298 |
1299 | Info mine = getInfo();
1300 |
1301 | float scaleX = info.mImgRect.width() / mine.mImgRect.width();
1302 | float scaleY = info.mImgRect.height() / mine.mImgRect.height();
1303 | float scale = scaleX < scaleY ? scaleX : scaleY;
1304 |
1305 | float ocx = info.mRect.left + info.mRect.width() / 2;
1306 | float ocy = info.mRect.top + info.mRect.height() / 2;
1307 |
1308 | float mcx = mine.mRect.left + mine.mRect.width() / 2;
1309 | float mcy = mine.mRect.top + mine.mRect.height() / 2;
1310 |
1311 | mAnimaMatrix.reset();
1312 | // mAnimaMatrix.postTranslate(-mBaseRect.left, -mBaseRect.top);
1313 | mAnimaMatrix.postTranslate(ocx - mcx, ocy - mcy);
1314 | mAnimaMatrix.postScale(scale, scale, ocx, ocy);
1315 | mAnimaMatrix.postRotate(info.mDegrees, ocx, ocy);
1316 | executeTranslate();
1317 |
1318 | mScaleCenter.set(ocx, ocy);
1319 | mRotateCenter.set(ocx, ocy);
1320 |
1321 | mTranslate.withTranslate(0, 0, (int) -(ocx - mcx), (int) -(ocy - mcy));
1322 | mTranslate.withScale(scale, 1);
1323 | mTranslate.withRotate((int) info.mDegrees, 0);
1324 |
1325 | if (info.mWidgetRect.width() < info.mImgRect.width() || info.mWidgetRect.height() < info.mImgRect.height()) {
1326 | float clipX = info.mWidgetRect.width() / info.mImgRect.width();
1327 | float clipY = info.mWidgetRect.height() / info.mImgRect.height();
1328 | clipX = clipX > 1 ? 1 : clipX;
1329 | clipY = clipY > 1 ? 1 : clipY;
1330 |
1331 | ClipCalculate c = info.mScaleType == ScaleType.FIT_START ? new START() : info.mScaleType == ScaleType.FIT_END ? new END() : new OTHER();
1332 |
1333 | mTranslate.withClip(clipX, clipY, 1 - clipX, 1 - clipY, mAnimaDuring / 3, c);
1334 |
1335 | mTmpMatrix.setScale(clipX, clipY, (mImgRect.left + mImgRect.right) / 2, c.calculateTop());
1336 | mTmpMatrix.mapRect(mTranslate.mClipRect, mImgRect);
1337 | mClip = mTranslate.mClipRect;
1338 | }
1339 |
1340 | mTranslate.start();
1341 | } else {
1342 | mFromInfo = info;
1343 | mInfoTime = System.currentTimeMillis();
1344 | }
1345 | }
1346 |
1347 | public void animaTo(Info info, Runnable completeCallBack) {
1348 | if (isInit) {
1349 | mTranslate.stop();
1350 |
1351 | mTranslateX = 0;
1352 | mTranslateY = 0;
1353 |
1354 | float tcx = info.mRect.left + info.mRect.width() / 2;
1355 | float tcy = info.mRect.top + info.mRect.height() / 2;
1356 |
1357 | mScaleCenter.set(mImgRect.left + mImgRect.width() / 2, mImgRect.top + mImgRect.height() / 2);
1358 | mRotateCenter.set(mScaleCenter);
1359 |
1360 | // 将图片旋转回正常位置,用以计算
1361 | mAnimaMatrix.postRotate(-mDegrees, mScaleCenter.x, mScaleCenter.y);
1362 | mAnimaMatrix.mapRect(mImgRect, mBaseRect);
1363 |
1364 | // 缩放
1365 | float scaleX = info.mImgRect.width() / mBaseRect.width();
1366 | float scaleY = info.mImgRect.height() / mBaseRect.height();
1367 | float scale = scaleX > scaleY ? scaleX : scaleY;
1368 |
1369 | mAnimaMatrix.postRotate(mDegrees, mScaleCenter.x, mScaleCenter.y);
1370 | mAnimaMatrix.mapRect(mImgRect, mBaseRect);
1371 |
1372 | mDegrees = mDegrees % 360;
1373 |
1374 | mTranslate.withTranslate(0, 0, (int) (tcx - mScaleCenter.x), (int) (tcy - mScaleCenter.y));
1375 | mTranslate.withScale(mScale, scale);
1376 | mTranslate.withRotate((int) mDegrees, (int) info.mDegrees, mAnimaDuring * 2 / 3);
1377 |
1378 | if (info.mWidgetRect.width() < info.mRect.width() || info.mWidgetRect.height() < info.mRect.height()) {
1379 | float clipX = info.mWidgetRect.width() / info.mRect.width();
1380 | float clipY = info.mWidgetRect.height() / info.mRect.height();
1381 | clipX = clipX > 1 ? 1 : clipX;
1382 | clipY = clipY > 1 ? 1 : clipY;
1383 |
1384 | final float cx = clipX;
1385 | final float cy = clipY;
1386 | final ClipCalculate c = info.mScaleType == ScaleType.FIT_START ? new START() : info.mScaleType == ScaleType.FIT_END ? new END() : new OTHER();
1387 |
1388 | postDelayed(new Runnable() {
1389 | @Override
1390 | public void run() {
1391 | mTranslate.withClip(1, 1, -1 + cx, -1 + cy, mAnimaDuring / 2, c);
1392 | }
1393 | }, mAnimaDuring / 2);
1394 | }
1395 |
1396 | mCompleteCallBack = completeCallBack;
1397 | mTranslate.start();
1398 | }
1399 | }
1400 | }
1401 |
--------------------------------------------------------------------------------
/library/src/main/java/com/itheima/library/RotateGestureDetector.java:
--------------------------------------------------------------------------------
1 | package com.itheima.library;
2 |
3 | import android.view.MotionEvent;
4 |
5 |
6 | public class RotateGestureDetector {
7 |
8 | private static final int MAX_DEGREES_STEP = 120;
9 |
10 | private OnRotateListener mListener;
11 |
12 | private float mPrevSlope;
13 | private float mCurrSlope;
14 |
15 | private float x1;
16 | private float y1;
17 | private float x2;
18 | private float y2;
19 |
20 | public RotateGestureDetector(OnRotateListener l) {
21 | mListener = l;
22 | }
23 |
24 | public void onTouchEvent(MotionEvent event) {
25 |
26 | final int Action = event.getActionMasked();
27 |
28 | switch (Action) {
29 | case MotionEvent.ACTION_POINTER_DOWN:
30 | case MotionEvent.ACTION_POINTER_UP:
31 | if (event.getPointerCount() == 2) mPrevSlope = caculateSlope(event);
32 | break;
33 | case MotionEvent.ACTION_MOVE:
34 | if (event.getPointerCount() > 1) {
35 | mCurrSlope = caculateSlope(event);
36 |
37 | double currDegrees = Math.toDegrees(Math.atan(mCurrSlope));
38 | double prevDegrees = Math.toDegrees(Math.atan(mPrevSlope));
39 |
40 | double deltaSlope = currDegrees - prevDegrees;
41 |
42 | if (Math.abs(deltaSlope) <= MAX_DEGREES_STEP) {
43 | mListener.onRotate((float) deltaSlope, (x2 + x1) / 2, (y2 + y1) / 2);
44 | }
45 | mPrevSlope = mCurrSlope;
46 | }
47 | break;
48 | default:
49 | break;
50 | }
51 | }
52 |
53 | private float caculateSlope(MotionEvent event) {
54 | x1 = event.getX(0);
55 | y1 = event.getY(0);
56 | x2 = event.getX(1);
57 | y2 = event.getY(1);
58 | return (y2 - y1) / (x2 - x1);
59 | }
60 | }
61 |
62 | interface OnRotateListener {
63 | void onRotate(float degrees, float focusX, float focusY);
64 | }
--------------------------------------------------------------------------------
/local.properties:
--------------------------------------------------------------------------------
1 | ## This file is automatically generated by Android Studio.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must *NOT* be checked into Version Control Systems,
5 | # as it contains information specific to your local configuration.
6 | #
7 | # Location of the SDK. This is only used by Gradle.
8 | # For customization when using a Version Control System, please read the
9 | # header note.
10 | #Wed Mar 01 13:52:20 CST 2017
11 | sdk.dir=i\:\\Users\\mwqi\\AppData\\Local\\Android\\sdk
12 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':library', ':app'
--------------------------------------------------------------------------------