7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/ant.properties:
--------------------------------------------------------------------------------
1 | # This file is used to override default values used by the Ant build system.
2 | #
3 | # This file must be checked into Version Control Systems, as it is
4 | # integral to the build system of your project.
5 |
6 | # This file is only used by the Ant script.
7 |
8 | # You can use this to override default values such as
9 | # 'source.dir' for the location of your java source folder and
10 | # 'out.dir' for the location of your output folder.
11 |
12 | # You can also use it define how the release builds are signed by declaring
13 | # the following properties:
14 | # 'key.store' for the location of your keystore and
15 | # 'key.alias' for the name of the key to use.
16 | # The password will be asked during the build when you use the 'release' target.
17 |
18 |
--------------------------------------------------------------------------------
/build.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
29 |
30 |
31 |
35 |
36 |
37 |
38 |
39 |
40 |
49 |
50 |
51 |
52 |
56 |
57 |
69 |
70 |
71 |
89 |
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/gen/com/hankkin/WeiXinLookImgsDemo/BuildConfig.java:
--------------------------------------------------------------------------------
1 | /*___Generated_by_IDEA___*/
2 |
3 | package com.hankkin.WeiXinLookImgsDemo;
4 |
5 | /* This stub is only used by the IDE. It is NOT the BuildConfig class actually packed into the APK */
6 | public final class BuildConfig {
7 | public final static boolean DEBUG = Boolean.parseBoolean(null);
8 | }
--------------------------------------------------------------------------------
/gen/com/hankkin/WeiXinLookImgsDemo/Manifest.java:
--------------------------------------------------------------------------------
1 | /*___Generated_by_IDEA___*/
2 |
3 | package com.hankkin.WeiXinLookImgsDemo;
4 |
5 | /* This stub is only used by the IDE. It is NOT the Manifest class actually packed into the APK */
6 | public final class Manifest {
7 | }
--------------------------------------------------------------------------------
/gen/com/hankkin/WeiXinLookImgsDemo/R.java:
--------------------------------------------------------------------------------
1 | /*___Generated_by_IDEA___*/
2 |
3 | package com.hankkin.WeiXinLookImgsDemo;
4 |
5 | /* This stub is only used by the IDE. It is NOT the R class actually packed into the APK */
6 | public final class R {
7 | }
--------------------------------------------------------------------------------
/libs/universal-image-loader-1.9.2-with-sources.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hankkin/WeiXinLookImgsDemo/5df2d1e825b75d1e57182b2503ff2d67a9a1bd30/libs/universal-image-loader-1.9.2-with-sources.jar
--------------------------------------------------------------------------------
/local.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
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 Ant
8 | # For customization when using a Version Control System, please read the
9 | # header note.
10 | sdk.dir=/Users/Hankkin/Documents/work/android/android-sdk-macosx
11 |
--------------------------------------------------------------------------------
/proguard-project.txt:
--------------------------------------------------------------------------------
1 | # To enable ProGuard in your project, edit project.properties
2 | # to define the proguard.config property as described in that file.
3 | #
4 | # Add project specific ProGuard rules here.
5 | # By default, the flags in this file are appended to flags specified
6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt
7 | # You can edit the include path and order by changing the ProGuard
8 | # include property in project.properties.
9 | #
10 | # For more details, see
11 | # http://developer.android.com/guide/developing/tools/proguard.html
12 |
13 | # Add any project specific keep options here:
14 |
15 | # If your project uses WebView with JS, uncomment the following
16 | # and specify the fully qualified class name to the JavaScript interface
17 | # class:
18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
19 | # public *;
20 | #}
21 |
--------------------------------------------------------------------------------
/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system edit
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 | #
10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
12 |
13 | # Project target.
14 | target=android-22
15 |
--------------------------------------------------------------------------------
/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hankkin/WeiXinLookImgsDemo/5df2d1e825b75d1e57182b2503ff2d67a9a1bd30/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/res/drawable-ldpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hankkin/WeiXinLookImgsDemo/5df2d1e825b75d1e57182b2503ff2d67a9a1bd30/res/drawable-ldpi/ic_launcher.png
--------------------------------------------------------------------------------
/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hankkin/WeiXinLookImgsDemo/5df2d1e825b75d1e57182b2503ff2d67a9a1bd30/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hankkin/WeiXinLookImgsDemo/5df2d1e825b75d1e57182b2503ff2d67a9a1bd30/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/res/layout/gridview_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
13 |
14 |
--------------------------------------------------------------------------------
/res/layout/image_detail_fragment.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
14 |
15 |
21 |
22 |
--------------------------------------------------------------------------------
/res/layout/image_detail_pager.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
11 |
12 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/res/layout/item.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
15 |
16 |
24 |
25 |
37 |
38 |
--------------------------------------------------------------------------------
/res/layout/main.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | WeiXinLookImgsDemo
4 | %1$d/%2$d
5 |
6 |
--------------------------------------------------------------------------------
/screenshot/QQ20151123-0@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hankkin/WeiXinLookImgsDemo/5df2d1e825b75d1e57182b2503ff2d67a9a1bd30/screenshot/QQ20151123-0@2x.png
--------------------------------------------------------------------------------
/screenshot/QQ20151123-1@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hankkin/WeiXinLookImgsDemo/5df2d1e825b75d1e57182b2503ff2d67a9a1bd30/screenshot/QQ20151123-1@2x.png
--------------------------------------------------------------------------------
/screenshot/QQ20151123-2@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hankkin/WeiXinLookImgsDemo/5df2d1e825b75d1e57182b2503ff2d67a9a1bd30/screenshot/QQ20151123-2@2x.png
--------------------------------------------------------------------------------
/screenshot/QQ20151123-3@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hankkin/WeiXinLookImgsDemo/5df2d1e825b75d1e57182b2503ff2d67a9a1bd30/screenshot/QQ20151123-3@2x.png
--------------------------------------------------------------------------------
/screenshot/screenshot.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hankkin/WeiXinLookImgsDemo/5df2d1e825b75d1e57182b2503ff2d67a9a1bd30/screenshot/screenshot.gif
--------------------------------------------------------------------------------
/src/com/hankkin/WeiXinLookImgsDemo/MyActivity.java:
--------------------------------------------------------------------------------
1 | package com.hankkin.WeiXinLookImgsDemo;
2 |
3 | import android.app.Activity;
4 | import android.os.Bundle;
5 | import android.widget.ListView;
6 |
7 | import com.hankkin.WeiXinLookImgsDemo.adapter.MyAdapter;
8 | import com.hankkin.WeiXinLookImgsDemo.model.ContentBean;
9 |
10 | import java.util.ArrayList;
11 | import java.util.List;
12 |
13 | public class MyActivity extends Activity {
14 | /*图片显示列表*/
15 | private ListView listView;
16 | /*图片URL数组*/
17 | private List contentBeans;
18 | /*说说适配器*/
19 | private MyAdapter adapter;
20 |
21 | /**
22 | * Called when the activity is first created.
23 | */
24 | @Override
25 | public void onCreate(Bundle savedInstanceState) {
26 | super.onCreate(savedInstanceState);
27 | setContentView(R.layout.main);
28 | initData();
29 | initViews();
30 | }
31 |
32 | /**
33 | * 初始化数据
34 | * by Hankkin at:2015-11-22 23:21:28
35 | */
36 | private void initData(){
37 | contentBeans = new ArrayList();
38 | ArrayList imgUrls1 = new ArrayList();
39 | imgUrls1.add("http://7xojuc.com1.z0.glb.clouddn.com/110H2E40-6.jpg?attname=&e=1448288962&token=KDsCqUAWz3P-YT6In6oPnT56Eh2cig4zgQd12SJ_:AlTjfYD9SBFOdB4jmmZuKXAMOp8");
40 | ContentBean cb1 = new ContentBean(1,"java","Sun Microsystems",imgUrls1);
41 | contentBeans.add(cb1);
42 |
43 | ArrayList imgUrls2 = new ArrayList();
44 | imgUrls2.add("http://7xojuc.com1.z0.glb.clouddn.com/110H2E40-6.jpg?attname=&e=1448288962&token=KDsCqUAWz3P-YT6In6oPnT56Eh2cig4zgQd12SJ_:AlTjfYD9SBFOdB4jmmZuKXAMOp8");
45 | imgUrls2.add("http://7xojuc.com1.z0.glb.clouddn.com/110H2E40-6.jpg?attname=&e=1448288962&token=KDsCqUAWz3P-YT6In6oPnT56Eh2cig4zgQd12SJ_:AlTjfYD9SBFOdB4jmmZuKXAMOp8");
46 | imgUrls2.add("http://7xojuc.com1.z0.glb.clouddn.com/110H2E40-6.jpg?attname=&e=1448288962&token=KDsCqUAWz3P-YT6In6oPnT56Eh2cig4zgQd12SJ_:AlTjfYD9SBFOdB4jmmZuKXAMOp8");
47 | ContentBean cb2 = new ContentBean(2,"OC","Stepstone",imgUrls2);
48 | contentBeans.add(cb2);
49 |
50 | ArrayList imgUrls3 = new ArrayList();
51 | imgUrls3.add("http://7xojuc.com1.z0.glb.clouddn.com/110H2E40-6.jpg?attname=&e=1448288962&token=KDsCqUAWz3P-YT6In6oPnT56Eh2cig4zgQd12SJ_:AlTjfYD9SBFOdB4jmmZuKXAMOp8");
52 | imgUrls3.add("http://7xojuc.com1.z0.glb.clouddn.com/110H2E40-6.jpg?attname=&e=1448288962&token=KDsCqUAWz3P-YT6In6oPnT56Eh2cig4zgQd12SJ_:AlTjfYD9SBFOdB4jmmZuKXAMOp8");
53 | imgUrls3.add("http://7xojuc.com1.z0.glb.clouddn.com/110H2E40-6.jpg?attname=&e=1448288962&token=KDsCqUAWz3P-YT6In6oPnT56Eh2cig4zgQd12SJ_:AlTjfYD9SBFOdB4jmmZuKXAMOp8");
54 | imgUrls3.add("http://7xojuc.com1.z0.glb.clouddn.com/110H2E40-6.jpg?attname=&e=1448288962&token=KDsCqUAWz3P-YT6In6oPnT56Eh2cig4zgQd12SJ_:AlTjfYD9SBFOdB4jmmZuKXAMOp8");
55 | imgUrls3.add("http://7xojuc.com1.z0.glb.clouddn.com/110H2E40-6.jpg?attname=&e=1448288962&token=KDsCqUAWz3P-YT6In6oPnT56Eh2cig4zgQd12SJ_:AlTjfYD9SBFOdB4jmmZuKXAMOp8");
56 | imgUrls3.add("http://7xojuc.com1.z0.glb.clouddn.com/110H2E40-6.jpg?attname=&e=1448288962&token=KDsCqUAWz3P-YT6In6oPnT56Eh2cig4zgQd12SJ_:AlTjfYD9SBFOdB4jmmZuKXAMOp8");
57 | imgUrls3.add("http://7xojuc.com1.z0.glb.clouddn.com/110H2E40-6.jpg?attname=&e=1448288962&token=KDsCqUAWz3P-YT6In6oPnT56Eh2cig4zgQd12SJ_:AlTjfYD9SBFOdB4jmmZuKXAMOp8");
58 | imgUrls3.add("http://7xojuc.com1.z0.glb.clouddn.com/110H2E40-6.jpg?attname=&e=1448288962&token=KDsCqUAWz3P-YT6In6oPnT56Eh2cig4zgQd12SJ_:AlTjfYD9SBFOdB4jmmZuKXAMOp8");
59 | imgUrls3.add("http://7xojuc.com1.z0.glb.clouddn.com/110H2E40-6.jpg?attname=&e=1448288962&token=KDsCqUAWz3P-YT6In6oPnT56Eh2cig4zgQd12SJ_:AlTjfYD9SBFOdB4jmmZuKXAMOp8");
60 | ContentBean cb3 = new ContentBean(3,"python","Guido van Rossum",imgUrls3);
61 | contentBeans.add(cb3);
62 | }
63 |
64 | /**
65 | * 初始化组件
66 | * by Hankkin at:2015-11-22 23:21:44
67 | */
68 | private void initViews(){
69 | listView = (ListView) findViewById(R.id.lv_my);
70 | adapter = new MyAdapter(MyActivity.this,contentBeans);
71 | listView.setAdapter(adapter);
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/com/hankkin/WeiXinLookImgsDemo/activty/ImageDetailFragment.java:
--------------------------------------------------------------------------------
1 | package com.hankkin.WeiXinLookImgsDemo.activty;
2 |
3 | import android.graphics.Bitmap;
4 | import android.os.Bundle;
5 | import android.support.v4.app.Fragment;
6 | import android.view.LayoutInflater;
7 | import android.view.View;
8 | import android.view.ViewGroup;
9 | import android.widget.ImageView;
10 | import android.widget.ProgressBar;
11 | import android.widget.Toast;
12 |
13 | import com.hankkin.WeiXinLookImgsDemo.R;
14 | import com.nostra13.universalimageloader.core.ImageLoader;
15 | import com.nostra13.universalimageloader.core.assist.FailReason;
16 | import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
17 | import com.others.PhotoViewAttacher;
18 |
19 | /**
20 | * 单张图片显示Fragment
21 | * Created by Hankkin on 15/11/22.
22 | */
23 | public class ImageDetailFragment extends Fragment {
24 | private String mImageUrl;
25 | private ImageView mImageView;
26 | private ProgressBar progressBar;
27 | private PhotoViewAttacher mAttacher;
28 |
29 | public static ImageDetailFragment newInstance(String imageUrl) {
30 | final ImageDetailFragment f = new ImageDetailFragment();
31 |
32 | final Bundle args = new Bundle();
33 | args.putString("url", imageUrl);
34 | f.setArguments(args);
35 |
36 | return f;
37 | }
38 |
39 | @Override
40 | public void onCreate(Bundle savedInstanceState) {
41 | super.onCreate(savedInstanceState);
42 | mImageUrl = getArguments() != null ? getArguments().getString("url") : null;
43 | }
44 |
45 | @Override
46 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
47 | final View v = inflater.inflate(R.layout.image_detail_fragment, container, false);
48 | mImageView = (ImageView) v.findViewById(R.id.image);
49 | mAttacher = new PhotoViewAttacher(mImageView);
50 |
51 | mAttacher.setOnPhotoTapListener(new PhotoViewAttacher.OnPhotoTapListener() {
52 |
53 | @Override
54 | public void onPhotoTap(View arg0, float arg1, float arg2) {
55 | getActivity().finish();
56 | }
57 | });
58 | mAttacher.setOnLongClickListener(new View.OnLongClickListener() {
59 | @Override
60 | public boolean onLongClick(View view) {
61 | Toast.makeText(getActivity().getApplicationContext(),"保存",Toast.LENGTH_SHORT).show();
62 | return false;
63 | }
64 | });
65 | progressBar = (ProgressBar) v.findViewById(R.id.loading);
66 | return v;
67 | }
68 |
69 | @Override
70 | public void onActivityCreated(Bundle savedInstanceState) {
71 | super.onActivityCreated(savedInstanceState);
72 |
73 | ImageLoader.getInstance().displayImage(mImageUrl, mImageView, new SimpleImageLoadingListener() {
74 | @Override
75 | public void onLoadingStarted(String imageUri, View view) {
76 | progressBar.setVisibility(View.VISIBLE);
77 | }
78 |
79 | @Override
80 | public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
81 | String message = null;
82 | switch (failReason.getType()) {
83 | case IO_ERROR:
84 | message = "下载错误";
85 | break;
86 | case DECODING_ERROR:
87 | message = "图片无法显示";
88 | break;
89 | case NETWORK_DENIED:
90 | message = "网络有问题,无法下载";
91 | break;
92 | case OUT_OF_MEMORY:
93 | message = "图片太大无法显示";
94 | break;
95 | case UNKNOWN:
96 | message = "未知的错误";
97 | break;
98 | }
99 | Toast.makeText(getActivity(), message, Toast.LENGTH_SHORT).show();
100 | progressBar.setVisibility(View.GONE);
101 | }
102 |
103 | @Override
104 | public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
105 | progressBar.setVisibility(View.GONE);
106 | mAttacher.update();
107 | }
108 | });
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/com/hankkin/WeiXinLookImgsDemo/activty/ImagePagerActivity.java:
--------------------------------------------------------------------------------
1 | package com.hankkin.WeiXinLookImgsDemo.activty;
2 |
3 | import java.util.ArrayList;
4 |
5 | import android.os.Bundle;
6 | import android.support.v4.app.Fragment;
7 | import android.support.v4.app.FragmentActivity;
8 | import android.support.v4.app.FragmentManager;
9 | import android.support.v4.app.FragmentStatePagerAdapter;
10 | import android.support.v4.view.ViewPager.OnPageChangeListener;
11 | import android.widget.TextView;
12 |
13 | import com.hankkin.WeiXinLookImgsDemo.R;
14 | import com.hankkin.WeiXinLookImgsDemo.view.HackyViewPager;
15 |
16 | /**
17 | * 图片查看器
18 | * Created by Hankkin on 15/11/22.
19 | */
20 | public class ImagePagerActivity extends FragmentActivity {
21 | private static final String STATE_POSITION = "STATE_POSITION";
22 | public static final String EXTRA_IMAGE_INDEX = "image_index";
23 | public static final String EXTRA_IMAGE_URLS = "image_urls";
24 |
25 | private HackyViewPager mPager;
26 | private int pagerPosition;
27 | private TextView indicator;
28 |
29 | @Override
30 | public void onCreate(Bundle savedInstanceState) {
31 | super.onCreate(savedInstanceState);
32 | setContentView(R.layout.image_detail_pager);
33 |
34 | pagerPosition = getIntent().getIntExtra(EXTRA_IMAGE_INDEX, 0);
35 | ArrayList urls = getIntent().getStringArrayListExtra(EXTRA_IMAGE_URLS);
36 |
37 | mPager = (HackyViewPager) findViewById(R.id.pager);
38 | ImagePagerAdapter mAdapter = new ImagePagerAdapter(getSupportFragmentManager(), urls);
39 | mPager.setAdapter(mAdapter);
40 | indicator = (TextView) findViewById(R.id.indicator);
41 |
42 | CharSequence text = getString(R.string.viewpager_indicator, 1, mPager.getAdapter().getCount());
43 | indicator.setText(text);
44 | // 更新下标
45 | mPager.setOnPageChangeListener(new OnPageChangeListener() {
46 |
47 | @Override
48 | public void onPageScrollStateChanged(int arg0) {
49 | }
50 |
51 | @Override
52 | public void onPageScrolled(int arg0, float arg1, int arg2) {
53 | }
54 |
55 | @Override
56 | public void onPageSelected(int arg0) {
57 | CharSequence text = getString(R.string.viewpager_indicator, arg0 + 1, mPager.getAdapter().getCount());
58 | indicator.setText(text);
59 | }
60 |
61 | });
62 | if (savedInstanceState != null) {
63 | pagerPosition = savedInstanceState.getInt(STATE_POSITION);
64 | }
65 |
66 | mPager.setCurrentItem(pagerPosition);
67 | }
68 |
69 | @Override
70 | public void onSaveInstanceState(Bundle outState) {
71 | outState.putInt(STATE_POSITION, mPager.getCurrentItem());
72 | }
73 |
74 | private class ImagePagerAdapter extends FragmentStatePagerAdapter {
75 |
76 | public ArrayList fileList;
77 |
78 | public ImagePagerAdapter(FragmentManager fm, ArrayList fileList) {
79 | super(fm);
80 | this.fileList = fileList;
81 | }
82 |
83 | @Override
84 | public int getCount() {
85 | return fileList == null ? 0 : fileList.size();
86 | }
87 |
88 | @Override
89 | public Fragment getItem(int position) {
90 | String url = fileList.get(position);
91 | return ImageDetailFragment.newInstance(url);
92 | }
93 |
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/com/hankkin/WeiXinLookImgsDemo/adapter/ImageGridAdapter.java:
--------------------------------------------------------------------------------
1 | package com.hankkin.WeiXinLookImgsDemo.adapter;
2 |
3 | import android.content.Context;
4 | import android.view.LayoutInflater;
5 | import android.view.View;
6 | import android.view.ViewGroup;
7 | import android.widget.BaseAdapter;
8 | import android.widget.ImageView;
9 |
10 | import com.hankkin.WeiXinLookImgsDemo.R;
11 | import com.nostra13.universalimageloader.core.ImageLoader;
12 |
13 | import java.util.List;
14 |
15 | /**
16 | * Created by Hankkin on 15/11/22.
17 | */
18 | public class ImageGridAdapter extends BaseAdapter {
19 |
20 | private Context context;
21 | private List imgUrls;
22 | private LayoutInflater inflater;
23 |
24 | public ImageGridAdapter(Context context, List imgUrls) {
25 | this.context = context;
26 | this.imgUrls = imgUrls;
27 | inflater = LayoutInflater.from(context);
28 | }
29 |
30 | @Override
31 | public int getCount() {
32 | return imgUrls.size();
33 | }
34 |
35 | @Override
36 | public Object getItem(int i) {
37 | return imgUrls.get(i);
38 | }
39 |
40 | @Override
41 | public long getItemId(int i) {
42 | return i;
43 | }
44 |
45 | @Override
46 | public View getView(int i, View view, ViewGroup viewGroup) {
47 | view = inflater.inflate(R.layout.gridview_item, null);
48 | ImageView imageView = (ImageView) view.findViewById(R.id.iv_content);
49 | ImageLoader.getInstance().displayImage(imgUrls.get(i), imageView);
50 | return view;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/com/hankkin/WeiXinLookImgsDemo/adapter/MyAdapter.java:
--------------------------------------------------------------------------------
1 | package com.hankkin.WeiXinLookImgsDemo.adapter;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 | import android.widget.AdapterView;
9 | import android.widget.BaseAdapter;
10 | import android.widget.TextView;
11 |
12 | import com.hankkin.WeiXinLookImgsDemo.activty.ImagePagerActivity;
13 | import com.hankkin.WeiXinLookImgsDemo.model.ContentBean;
14 | import com.hankkin.WeiXinLookImgsDemo.view.NoScrollGridView;
15 | import com.hankkin.WeiXinLookImgsDemo.R;
16 |
17 | import java.io.Serializable;
18 | import java.util.List;
19 |
20 | /**
21 | * Created by Hankkin on 15/11/22.
22 | */
23 | public class MyAdapter extends BaseAdapter {
24 |
25 | private Context context;
26 | private List data;
27 |
28 | public MyAdapter(Context context, List data) {
29 | this.context = context;
30 | this.data = data;
31 | }
32 |
33 | @Override
34 | public int getCount() {
35 | return data.size();
36 | }
37 |
38 | @Override
39 | public Object getItem(int i) {
40 | return data.get(i);
41 | }
42 |
43 | @Override
44 | public long getItemId(int i) {
45 | return i;
46 | }
47 |
48 | @Override
49 | public View getView(int i, View view, ViewGroup viewGroup) {
50 | ViewHolder holder;
51 | if (view == null) {
52 | holder = new ViewHolder();
53 | view = View.inflate(context, R.layout.item, null);
54 | holder.gridView = (NoScrollGridView) view.findViewById(R.id.gridview);
55 | holder.tvName = (TextView) view.findViewById(R.id.tv_name);
56 | holder.tvTitle = (TextView) view.findViewById(R.id.tv_title);
57 | view.setTag(holder);
58 | } else {
59 | holder = (ViewHolder) view.getTag();
60 | }
61 |
62 | final ContentBean bean = data.get(i);
63 |
64 | holder.tvName.setText(bean.getName());
65 | holder.tvTitle.setText(bean.getTitle());
66 |
67 | if (data != null && data.size() > 0) {
68 | holder.gridView.setAdapter(new ImageGridAdapter(context, bean.getImgUrls()));
69 | }
70 |
71 | /**
72 | * 图片列表点击事件
73 | */
74 | holder.gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
75 | @Override
76 | public void onItemClick(AdapterView> adapterView, View view, int i, long l) {
77 | Intent intent = new Intent(context, ImagePagerActivity.class);
78 | intent.putExtra(ImagePagerActivity.EXTRA_IMAGE_URLS, (Serializable) bean.getImgUrls());
79 | intent.putExtra(ImagePagerActivity.EXTRA_IMAGE_INDEX, i);
80 | context.startActivity(intent);
81 | }
82 | });
83 |
84 | return view;
85 | }
86 |
87 | class ViewHolder {
88 | TextView tvName, tvTitle;
89 | NoScrollGridView gridView;
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/com/hankkin/WeiXinLookImgsDemo/application/MyApplication.java:
--------------------------------------------------------------------------------
1 | package com.hankkin.WeiXinLookImgsDemo.application;
2 |
3 | import android.app.Application;
4 | import android.content.Context;
5 | import android.graphics.Bitmap;
6 | import android.os.Environment;
7 | import android.os.Handler;
8 |
9 | import com.hankkin.WeiXinLookImgsDemo.R;
10 | import com.nostra13.universalimageloader.cache.disc.impl.UnlimitedDiscCache;
11 | import com.nostra13.universalimageloader.cache.disc.naming.HashCodeFileNameGenerator;
12 | import com.nostra13.universalimageloader.cache.memory.impl.LruMemoryCache;
13 | import com.nostra13.universalimageloader.core.DisplayImageOptions;
14 | import com.nostra13.universalimageloader.core.ImageLoader;
15 | import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
16 | import com.nostra13.universalimageloader.core.assist.ImageScaleType;
17 | import com.nostra13.universalimageloader.core.assist.QueueProcessingType;
18 | import com.nostra13.universalimageloader.core.decode.BaseImageDecoder;
19 | import com.nostra13.universalimageloader.core.display.SimpleBitmapDisplayer;
20 | import com.nostra13.universalimageloader.core.download.BaseImageDownloader;
21 |
22 | import java.io.File;
23 |
24 | /**
25 | * Created by Hankkin on 15/11/22.
26 | */
27 | public class MyApplication extends Application {
28 |
29 | private MyApplication context;
30 |
31 | @Override
32 | public void onCreate() {
33 | super.onCreate();
34 | context = this;
35 | initImageLoader(context);
36 | }
37 |
38 | /**
39 | * 初始化Imageloader
40 | * by Hankkin at:2015-11-22 23:20:29
41 | * @param context
42 | */
43 | public static void initImageLoader(Context context){
44 | DisplayImageOptions options = new DisplayImageOptions.Builder()
45 | .showImageOnLoading(R.drawable.ic_launcher)
46 | .showImageOnFail(R.drawable.ic_launcher)
47 | .resetViewBeforeLoading(false) // default
48 | .delayBeforeLoading(0)
49 | .cacheInMemory(true) // default
50 | .cacheOnDisk(true) // default
51 | .considerExifParams(true) // default
52 | .imageScaleType(ImageScaleType.IN_SAMPLE_POWER_OF_2) // default
53 | .bitmapConfig(Bitmap.Config.ARGB_8888) // default
54 | .displayer(new SimpleBitmapDisplayer()) // default
55 | .handler(new Handler()) // default
56 | .build();
57 | File picPath = new File(Environment.getExternalStorageDirectory().getPath() + File.separator + "weixinlookimgdemo" + File.separator + "files");
58 |
59 | ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context)
60 | .memoryCacheExtraOptions(480, 800) // default = device screen dimensions
61 | .diskCacheExtraOptions(480, 800, null)
62 | .threadPoolSize(3) // default
63 | .threadPriority(Thread.NORM_PRIORITY - 1) // default
64 | .tasksProcessingOrder(QueueProcessingType.FIFO) // default
65 | .denyCacheImageMultipleSizesInMemory()
66 | .memoryCache(new LruMemoryCache(2 * 1024 * 1024))
67 | .memoryCacheSize(2 * 1024 * 1024)
68 | .memoryCacheSizePercentage(13) // default
69 | .diskCache(new UnlimitedDiscCache(picPath)) // default
70 | .diskCacheSize(50 * 1024 * 1024)
71 | .diskCacheFileCount(1000)
72 | .diskCacheFileNameGenerator(new HashCodeFileNameGenerator()) // default
73 | .imageDownloader(new BaseImageDownloader(context)) // default
74 | .imageDecoder(new BaseImageDecoder(true)) // default
75 | .defaultDisplayImageOptions(options) // default
76 | .writeDebugLogs()
77 | .build();
78 | // Initialize ImageLoader with configuration.
79 | ImageLoader.getInstance().init(config);
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/com/hankkin/WeiXinLookImgsDemo/model/ContentBean.java:
--------------------------------------------------------------------------------
1 | package com.hankkin.WeiXinLookImgsDemo.model;
2 |
3 | import java.io.Serializable;
4 | import java.util.ArrayList;
5 | import java.util.List;
6 |
7 | /**
8 | * Created by Hankkin on 15/11/22.
9 | * 说说的实体类
10 | */
11 | public class ContentBean implements Serializable{
12 |
13 | private int id; //id
14 | private String name; //姓名
15 | private String title; //标题
16 | private ArrayList imgUrls; //图片数组
17 |
18 | public ContentBean(int id, String name, String title, ArrayList imgUrls) {
19 | this.id = id;
20 | this.name = name;
21 | this.title = title;
22 | this.imgUrls = imgUrls;
23 | }
24 |
25 | public int getId() {
26 | return id;
27 | }
28 |
29 | public void setId(int id) {
30 | this.id = id;
31 | }
32 |
33 | public String getName() {
34 | return name;
35 | }
36 |
37 | public void setName(String name) {
38 | this.name = name;
39 | }
40 |
41 | public String getTitle() {
42 | return title;
43 | }
44 |
45 | public void setTitle(String title) {
46 | this.title = title;
47 | }
48 |
49 | public List getImgUrls() {
50 | return imgUrls;
51 | }
52 |
53 | public void setImgUrls(ArrayList imgUrls) {
54 | this.imgUrls = imgUrls;
55 | }
56 |
57 | @Override
58 | public String toString() {
59 | return "ContentBean{" +
60 | "id=" + id +
61 | ", name='" + name + '\'' +
62 | ", title='" + title + '\'' +
63 | ", imgUrls=" + imgUrls +
64 | '}';
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/com/hankkin/WeiXinLookImgsDemo/view/HackyViewPager.java:
--------------------------------------------------------------------------------
1 | package com.hankkin.WeiXinLookImgsDemo.view;
2 |
3 | import android.content.Context;
4 | import android.support.v4.view.ViewPager;
5 | import android.util.AttributeSet;
6 | import android.util.Log;
7 | import android.view.MotionEvent;
8 |
9 | /**
10 | * Hacky fix for Issue #4 and
11 | * http://code.google.com/p/android/issues/detail?id=18990
12 | *
13 | * ScaleGestureDetector seems to mess up the touch events, which means that
14 | * ViewGroups which make use of onInterceptTouchEvent throw a lot of
15 | * IllegalArgumentException: pointerIndex out of range.
16 | *
17 | * There's not much I can do in my code for now, but we can mask the result by
18 | * just catching the problem and ignoring it.
19 | *
20 | * @author Chris Banes
21 | */
22 | public class HackyViewPager extends ViewPager {
23 |
24 | private static final String TAG = "HackyViewPager";
25 |
26 | public HackyViewPager(Context context) {
27 | super(context);
28 | }
29 |
30 | public HackyViewPager(Context context, AttributeSet attrs) {
31 | super(context, attrs);
32 | }
33 |
34 | @Override
35 | public boolean onInterceptTouchEvent(MotionEvent ev) {
36 | try {
37 | return super.onInterceptTouchEvent(ev);
38 | } catch (IllegalArgumentException e) {
39 | // 不理会
40 | Log.e(TAG, "hacky viewpager error1");
41 | return false;
42 | } catch (ArrayIndexOutOfBoundsException e) {
43 | // 不理会
44 | Log.e(TAG, "hacky viewpager error2");
45 | return false;
46 | }
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/src/com/hankkin/WeiXinLookImgsDemo/view/IPhotoView.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright 2011, 2012 Chris Banes.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *******************************************************************************/
16 | package com.hankkin.WeiXinLookImgsDemo.view;
17 |
18 | import android.graphics.Bitmap;
19 | import android.graphics.Matrix;
20 | import android.graphics.RectF;
21 | import android.view.GestureDetector;
22 | import android.view.View;
23 | import android.widget.ImageView;
24 |
25 | import com.others.PhotoViewAttacher;
26 |
27 |
28 | public interface IPhotoView {
29 |
30 | public static final float DEFAULT_MAX_SCALE = 3.0f;
31 | public static final float DEFAULT_MID_SCALE = 1.75f;
32 | public static final float DEFAULT_MIN_SCALE = 1.0f;
33 | public static final int DEFAULT_ZOOM_DURATION = 200;
34 |
35 | /**
36 | * Returns true if the PhotoView is set to allow zooming of Photos.
37 | *
38 | * @return true if the PhotoView allows zooming.
39 | */
40 | boolean canZoom();
41 |
42 | /**
43 | * Gets the Display Rectangle of the currently displayed Drawable. The Rectangle is relative to
44 | * this View and includes all scaling and translations.
45 | *
46 | * @return - RectF of Displayed Drawable
47 | */
48 | RectF getDisplayRect();
49 |
50 | /**
51 | * Sets the Display Matrix of the currently displayed Drawable. The Rectangle is considered
52 | * relative to this View and includes all scaling and translations.
53 | *
54 | * @param finalMatrix target matrix to set PhotoView to
55 | * @return - true if rectangle was applied successfully
56 | */
57 | boolean setDisplayMatrix(Matrix finalMatrix);
58 |
59 | /**
60 | * Gets the Display Matrix of the currently displayed Drawable. The Rectangle is considered
61 | * relative to this View and includes all scaling and translations.
62 | *
63 | * @return - true if rectangle was applied successfully
64 | */
65 | Matrix getDisplayMatrix();
66 |
67 | /**
68 | * Use {@link #getMinimumScale()} instead, this will be removed in future release
69 | *
70 | * @return The current minimum scale level. What this value represents depends on the current
71 | * {@link android.widget.ImageView.ScaleType}.
72 | */
73 | @Deprecated
74 | float getMinScale();
75 |
76 | /**
77 | * @return The current minimum scale level. What this value represents depends on the current
78 | * {@link android.widget.ImageView.ScaleType}.
79 | */
80 | float getMinimumScale();
81 |
82 | /**
83 | * Use {@link #getMediumScale()} instead, this will be removed in future release
84 | *
85 | * @return The current middle scale level. What this value represents depends on the current
86 | * {@link android.widget.ImageView.ScaleType}.
87 | */
88 | @Deprecated
89 | float getMidScale();
90 |
91 | /**
92 | * @return The current medium scale level. What this value represents depends on the current
93 | * {@link android.widget.ImageView.ScaleType}.
94 | */
95 | float getMediumScale();
96 |
97 | /**
98 | * Use {@link #getMaximumScale()} instead, this will be removed in future release
99 | *
100 | * @return The current maximum scale level. What this value represents depends on the current
101 | * {@link android.widget.ImageView.ScaleType}.
102 | */
103 | @Deprecated
104 | float getMaxScale();
105 |
106 | /**
107 | * @return The current maximum scale level. What this value represents depends on the current
108 | * {@link android.widget.ImageView.ScaleType}.
109 | */
110 | float getMaximumScale();
111 |
112 | /**
113 | * Returns the current scale value
114 | *
115 | * @return float - current scale value
116 | */
117 | float getScale();
118 |
119 | /**
120 | * Return the current scale type in use by the ImageView.
121 | *
122 | * @return current ImageView.ScaleType
123 | */
124 | ImageView.ScaleType getScaleType();
125 |
126 | /**
127 | * Whether to allow the ImageView's parent to intercept the touch event when the photo is scroll
128 | * to it's horizontal edge.
129 | *
130 | * @param allow whether to allow intercepting by parent element or not
131 | */
132 | void setAllowParentInterceptOnEdge(boolean allow);
133 |
134 | /**
135 | * Use {@link #setMinimumScale(float minimumScale)} instead, this will be removed in future
136 | * release
137 | *
138 | * Sets the minimum scale level. What this value represents depends on the current {@link
139 | * android.widget.ImageView.ScaleType}.
140 | *
141 | * @param minScale minimum allowed scale
142 | */
143 | @Deprecated
144 | void setMinScale(float minScale);
145 |
146 | /**
147 | * Sets the minimum scale level. What this value represents depends on the current {@link
148 | * android.widget.ImageView.ScaleType}.
149 | *
150 | * @param minimumScale minimum allowed scale
151 | */
152 | void setMinimumScale(float minimumScale);
153 |
154 | /**
155 | * Use {@link #setMediumScale(float mediumScale)} instead, this will be removed in future
156 | * release
157 | *
158 | * Sets the middle scale level. What this value represents depends on the current {@link
159 | * android.widget.ImageView.ScaleType}.
160 | *
161 | * @param midScale medium scale preset
162 | */
163 | @Deprecated
164 | void setMidScale(float midScale);
165 |
166 | /*
167 | * Sets the medium scale level. What this value represents depends on the current {@link android.widget.ImageView.ScaleType}.
168 | *
169 | * @param mediumScale medium scale preset
170 | */
171 | void setMediumScale(float mediumScale);
172 |
173 | /**
174 | * Use {@link #setMaximumScale(float maximumScale)} instead, this will be removed in future
175 | * release
176 | *
177 | * Sets the maximum scale level. What this value represents depends on the current {@link
178 | * android.widget.ImageView.ScaleType}.
179 | *
180 | * @param maxScale maximum allowed scale preset
181 | */
182 | @Deprecated
183 | void setMaxScale(float maxScale);
184 |
185 | /**
186 | * Sets the maximum scale level. What this value represents depends on the current {@link
187 | * android.widget.ImageView.ScaleType}.
188 | *
189 | * @param maximumScale maximum allowed scale preset
190 | */
191 | void setMaximumScale(float maximumScale);
192 |
193 | /**
194 | * Register a callback to be invoked when the Photo displayed by this view is long-pressed.
195 | *
196 | * @param listener - Listener to be registered.
197 | */
198 | void setOnLongClickListener(View.OnLongClickListener listener);
199 |
200 | /**
201 | * Register a callback to be invoked when the Matrix has changed for this View. An example would
202 | * be the user panning or scaling the Photo.
203 | *
204 | * @param listener - Listener to be registered.
205 | */
206 | void setOnMatrixChangeListener(PhotoViewAttacher.OnMatrixChangedListener listener);
207 |
208 | /**
209 | * Register a callback to be invoked when the Photo displayed by this View is tapped with a
210 | * single tap.
211 | *
212 | * @param listener - Listener to be registered.
213 | */
214 | void setOnPhotoTapListener(PhotoViewAttacher.OnPhotoTapListener listener);
215 |
216 | /**
217 | * Returns a listener to be invoked when the Photo displayed by this View is tapped with a
218 | * single tap.
219 | *
220 | * @return PhotoViewAttacher.OnPhotoTapListener currently set, may be null
221 | */
222 | PhotoViewAttacher.OnPhotoTapListener getOnPhotoTapListener();
223 |
224 | /**
225 | * Register a callback to be invoked when the View is tapped with a single tap.
226 | *
227 | * @param listener - Listener to be registered.
228 | */
229 | void setOnViewTapListener(PhotoViewAttacher.OnViewTapListener listener);
230 |
231 | /**
232 | * Enables rotation via PhotoView internal functions.
233 | *
234 | * @param rotationDegree - Degree to rotate PhotoView to, should be in range 0 to 360
235 | */
236 | void setRotationTo(float rotationDegree);
237 |
238 | /**
239 | * Enables rotation via PhotoView internal functions.
240 | *
241 | * @param rotationDegree - Degree to rotate PhotoView by, should be in range 0 to 360
242 | */
243 | void setRotationBy(float rotationDegree);
244 |
245 | /**
246 | * Returns a callback listener to be invoked when the View is tapped with a single tap.
247 | *
248 | * @return PhotoViewAttacher.OnViewTapListener currently set, may be null
249 | */
250 | PhotoViewAttacher.OnViewTapListener getOnViewTapListener();
251 |
252 | /**
253 | * Changes the current scale to the specified value.
254 | *
255 | * @param scale - Value to scale to
256 | */
257 | void setScale(float scale);
258 |
259 | /**
260 | * Changes the current scale to the specified value.
261 | *
262 | * @param scale - Value to scale to
263 | * @param animate - Whether to animate the scale
264 | */
265 | void setScale(float scale, boolean animate);
266 |
267 | /**
268 | * Changes the current scale to the specified value, around the given focal point.
269 | *
270 | * @param scale - Value to scale to
271 | * @param focalX - X Focus Point
272 | * @param focalY - Y Focus Point
273 | * @param animate - Whether to animate the scale
274 | */
275 | void setScale(float scale, float focalX, float focalY, boolean animate);
276 |
277 | /**
278 | * Controls how the image should be resized or moved to match the size of the ImageView. Any
279 | * scaling or panning will happen within the confines of this {@link
280 | * android.widget.ImageView.ScaleType}.
281 | *
282 | * @param scaleType - The desired scaling mode.
283 | */
284 | void setScaleType(ImageView.ScaleType scaleType);
285 |
286 | /**
287 | * Allows you to enable/disable the zoom functionality on the ImageView. When disable the
288 | * ImageView reverts to using the FIT_CENTER matrix.
289 | *
290 | * @param zoomable - Whether the zoom functionality is enabled.
291 | */
292 | void setZoomable(boolean zoomable);
293 |
294 | /**
295 | * Enables rotation via PhotoView internal functions. Name is chosen so it won't collide with
296 | * View.setRotation(float) in API since 11
297 | *
298 | * @param rotationDegree - Degree to rotate PhotoView to, should be in range 0 to 360
299 | * @deprecated use {@link #setRotationTo(float)}
300 | */
301 | void setPhotoViewRotation(float rotationDegree);
302 |
303 | /**
304 | * Extracts currently visible area to Bitmap object, if there is no image loaded yet or the
305 | * ImageView is already destroyed, returns {@code null}
306 | *
307 | * @return currently visible area as bitmap or null
308 | */
309 | Bitmap getVisibleRectangleBitmap();
310 |
311 | /**
312 | * Allows to change zoom transition speed, default value is 200 (PhotoViewAttacher.DEFAULT_ZOOM_DURATION).
313 | * Will default to 200 if provided negative value
314 | *
315 | * @param milliseconds duration of zoom interpolation
316 | */
317 | void setZoomTransitionDuration(int milliseconds);
318 |
319 | /**
320 | * Will return instance of IPhotoView (eg. PhotoViewAttacher), can be used to provide better
321 | * integration
322 | *
323 | * @return IPhotoView implementation instance if available, null if not
324 | */
325 | IPhotoView getIPhotoViewImplementation();
326 |
327 | /**
328 | * Sets custom double tap listener, to intercept default given functions. To reset behavior to
329 | * default, you can just pass in "null" or public field of PhotoViewAttacher.defaultOnDoubleTapListener
330 | *
331 | * @param newOnDoubleTapListener custom OnDoubleTapListener to be set on ImageView
332 | */
333 | public void setOnDoubleTapListener(GestureDetector.OnDoubleTapListener newOnDoubleTapListener);
334 | }
335 |
--------------------------------------------------------------------------------
/src/com/hankkin/WeiXinLookImgsDemo/view/NoScrollGridView.java:
--------------------------------------------------------------------------------
1 | package com.hankkin.WeiXinLookImgsDemo.view;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.widget.GridView;
6 |
7 | /**
8 | * Created by Hankkin on 15/11/22.
9 | * 屏蔽掉GridView的滚动,防止嵌入ScrollView时出现冲突
10 | * by:Hankkin at:2015-08-05 11:31:06
11 | *
12 | */
13 | public class NoScrollGridView extends GridView {
14 |
15 | public NoScrollGridView(Context context) {
16 | super(context);
17 | }
18 |
19 | public NoScrollGridView(Context context, AttributeSet attrs) {
20 | super(context, attrs);
21 | }
22 |
23 | public NoScrollGridView(Context context, AttributeSet attrs, int defStyle) {
24 | super(context, attrs, defStyle);
25 | }
26 |
27 | //该自定义控件只是重写了GridView的onMeasure方法,使其不会出现滚动条,ScrollView嵌套ListView也是同样的道理,不再赘述。
28 | @Override
29 | public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
30 | int expandSpec = MeasureSpec.makeMeasureSpec(
31 | Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
32 | super.onMeasure(widthMeasureSpec, expandSpec);
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/src/com/others/Compat.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright 2011, 2012 Chris Banes.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *******************************************************************************/
16 | package com.others;
17 |
18 | import android.annotation.TargetApi;
19 | import android.os.Build;
20 | import android.os.Build.VERSION;
21 | import android.os.Build.VERSION_CODES;
22 | import android.view.MotionEvent;
23 | import android.view.View;
24 |
25 | public class Compat {
26 |
27 | private static final int SIXTY_FPS_INTERVAL = 1000 / 60;
28 |
29 | public static void postOnAnimation(View view, Runnable runnable) {
30 | if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
31 | postOnAnimationJellyBean(view, runnable);
32 | } else {
33 | view.postDelayed(runnable, SIXTY_FPS_INTERVAL);
34 | }
35 | }
36 |
37 | @TargetApi(16)
38 | private static void postOnAnimationJellyBean(View view, Runnable runnable) {
39 | view.postOnAnimation(runnable);
40 | }
41 |
42 | public static int getPointerIndex(int action) {
43 | if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB)
44 | return getPointerIndexHoneyComb(action);
45 | else
46 | return getPointerIndexEclair(action);
47 | }
48 |
49 | @SuppressWarnings("deprecation")
50 | @TargetApi(Build.VERSION_CODES.ECLAIR)
51 | private static int getPointerIndexEclair(int action) {
52 | return (action & MotionEvent.ACTION_POINTER_ID_MASK) >> MotionEvent.ACTION_POINTER_ID_SHIFT;
53 | }
54 |
55 | @TargetApi(Build.VERSION_CODES.HONEYCOMB)
56 | private static int getPointerIndexHoneyComb(int action) {
57 | return (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
58 | }
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/src/com/others/DefaultOnDoubleTapListener.java:
--------------------------------------------------------------------------------
1 | package com.others;
2 |
3 | import android.graphics.RectF;
4 | import android.view.GestureDetector;
5 | import android.view.MotionEvent;
6 | import android.widget.ImageView;
7 |
8 | /**
9 | * Provided default implementation of GestureDetector.OnDoubleTapListener, to be overriden with custom behavior, if needed
10 | *
11 | */
12 | public class DefaultOnDoubleTapListener implements GestureDetector.OnDoubleTapListener {
13 |
14 | private PhotoViewAttacher photoViewAttacher;
15 |
16 | /**
17 | * Default constructor
18 | *
19 | * @param photoViewAttacher PhotoViewAttacher to bind to
20 | */
21 | public DefaultOnDoubleTapListener(PhotoViewAttacher photoViewAttacher) {
22 | setPhotoViewAttacher(photoViewAttacher);
23 | }
24 |
25 | /**
26 | * Allows to change PhotoViewAttacher within range of single instance
27 | *
28 | * @param newPhotoViewAttacher PhotoViewAttacher to bind to
29 | */
30 | public void setPhotoViewAttacher(PhotoViewAttacher newPhotoViewAttacher) {
31 | this.photoViewAttacher = newPhotoViewAttacher;
32 | }
33 |
34 | @Override
35 | public boolean onSingleTapConfirmed(MotionEvent e) {
36 | if (this.photoViewAttacher == null)
37 | return false;
38 |
39 | ImageView imageView = photoViewAttacher.getImageView();
40 |
41 | if (null != photoViewAttacher.getOnPhotoTapListener()) {
42 | final RectF displayRect = photoViewAttacher.getDisplayRect();
43 |
44 | if (null != displayRect) {
45 | final float x = e.getX(), y = e.getY();
46 |
47 | // Check to see if the user tapped on the photo
48 | if (displayRect.contains(x, y)) {
49 |
50 | float xResult = (x - displayRect.left)
51 | / displayRect.width();
52 | float yResult = (y - displayRect.top)
53 | / displayRect.height();
54 |
55 | photoViewAttacher.getOnPhotoTapListener().onPhotoTap(imageView, xResult, yResult);
56 | return true;
57 | }
58 | }
59 | }
60 | if (null != photoViewAttacher.getOnViewTapListener()) {
61 | photoViewAttacher.getOnViewTapListener().onViewTap(imageView, e.getX(), e.getY());
62 | }
63 |
64 | return false;
65 | }
66 |
67 | @Override
68 | public boolean onDoubleTap(MotionEvent ev) {
69 | if (photoViewAttacher == null)
70 | return false;
71 |
72 | try {
73 | float scale = photoViewAttacher.getScale();
74 | float x = ev.getX();
75 | float y = ev.getY();
76 |
77 | if (scale < photoViewAttacher.getMediumScale()) {
78 | photoViewAttacher.setScale(photoViewAttacher.getMediumScale(), x, y, true);
79 | } else if (scale >= photoViewAttacher.getMediumScale() && scale < photoViewAttacher.getMaximumScale()) {
80 | photoViewAttacher.setScale(photoViewAttacher.getMaximumScale(), x, y, true);
81 | } else {
82 | photoViewAttacher.setScale(photoViewAttacher.getMinimumScale(), x, y, true);
83 | }
84 | } catch (ArrayIndexOutOfBoundsException e) {
85 | // Can sometimes happen when getX() and getY() is called
86 | }
87 |
88 | return true;
89 | }
90 |
91 | @Override
92 | public boolean onDoubleTapEvent(MotionEvent e) {
93 | // Wait for the confirmed onDoubleTap() instead
94 | return false;
95 | }
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/src/com/others/IPhotoView.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright 2011, 2012 Chris Banes.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *******************************************************************************/
16 | package com.others;
17 |
18 | import android.graphics.Bitmap;
19 | import android.graphics.Matrix;
20 | import android.graphics.RectF;
21 | import android.view.GestureDetector;
22 | import android.view.View;
23 | import android.widget.ImageView;
24 |
25 |
26 | public interface IPhotoView {
27 |
28 | public static final float DEFAULT_MAX_SCALE = 3.0f;
29 | public static final float DEFAULT_MID_SCALE = 1.75f;
30 | public static final float DEFAULT_MIN_SCALE = 1.0f;
31 | public static final int DEFAULT_ZOOM_DURATION = 200;
32 |
33 | /**
34 | * Returns true if the PhotoView is set to allow zooming of Photos.
35 | *
36 | * @return true if the PhotoView allows zooming.
37 | */
38 | boolean canZoom();
39 |
40 | /**
41 | * Gets the Display Rectangle of the currently displayed Drawable. The Rectangle is relative to
42 | * this View and includes all scaling and translations.
43 | *
44 | * @return - RectF of Displayed Drawable
45 | */
46 | RectF getDisplayRect();
47 |
48 | /**
49 | * Sets the Display Matrix of the currently displayed Drawable. The Rectangle is considered
50 | * relative to this View and includes all scaling and translations.
51 | *
52 | * @param finalMatrix target matrix to set PhotoView to
53 | * @return - true if rectangle was applied successfully
54 | */
55 | boolean setDisplayMatrix(Matrix finalMatrix);
56 |
57 | /**
58 | * Gets the Display Matrix of the currently displayed Drawable. The Rectangle is considered
59 | * relative to this View and includes all scaling and translations.
60 | *
61 | * @return - true if rectangle was applied successfully
62 | */
63 | Matrix getDisplayMatrix();
64 |
65 | /**
66 | * Use {@link #getMinimumScale()} instead, this will be removed in future release
67 | *
68 | * @return The current minimum scale level. What this value represents depends on the current
69 | * {@link android.widget.ImageView.ScaleType}.
70 | */
71 | @Deprecated
72 | float getMinScale();
73 |
74 | /**
75 | * @return The current minimum scale level. What this value represents depends on the current
76 | * {@link android.widget.ImageView.ScaleType}.
77 | */
78 | float getMinimumScale();
79 |
80 | /**
81 | * Use {@link #getMediumScale()} instead, this will be removed in future release
82 | *
83 | * @return The current middle scale level. What this value represents depends on the current
84 | * {@link android.widget.ImageView.ScaleType}.
85 | */
86 | @Deprecated
87 | float getMidScale();
88 |
89 | /**
90 | * @return The current medium scale level. What this value represents depends on the current
91 | * {@link android.widget.ImageView.ScaleType}.
92 | */
93 | float getMediumScale();
94 |
95 | /**
96 | * Use {@link #getMaximumScale()} instead, this will be removed in future release
97 | *
98 | * @return The current maximum scale level. What this value represents depends on the current
99 | * {@link android.widget.ImageView.ScaleType}.
100 | */
101 | @Deprecated
102 | float getMaxScale();
103 |
104 | /**
105 | * @return The current maximum scale level. What this value represents depends on the current
106 | * {@link android.widget.ImageView.ScaleType}.
107 | */
108 | float getMaximumScale();
109 |
110 | /**
111 | * Returns the current scale value
112 | *
113 | * @return float - current scale value
114 | */
115 | float getScale();
116 |
117 | /**
118 | * Return the current scale type in use by the ImageView.
119 | *
120 | * @return current ImageView.ScaleType
121 | */
122 | ImageView.ScaleType getScaleType();
123 |
124 | /**
125 | * Whether to allow the ImageView's parent to intercept the touch event when the photo is scroll
126 | * to it's horizontal edge.
127 | *
128 | * @param allow whether to allow intercepting by parent element or not
129 | */
130 | void setAllowParentInterceptOnEdge(boolean allow);
131 |
132 | /**
133 | * Use {@link #setMinimumScale(float minimumScale)} instead, this will be removed in future
134 | * release
135 | *
136 | * Sets the minimum scale level. What this value represents depends on the current {@link
137 | * android.widget.ImageView.ScaleType}.
138 | *
139 | * @param minScale minimum allowed scale
140 | */
141 | @Deprecated
142 | void setMinScale(float minScale);
143 |
144 | /**
145 | * Sets the minimum scale level. What this value represents depends on the current {@link
146 | * android.widget.ImageView.ScaleType}.
147 | *
148 | * @param minimumScale minimum allowed scale
149 | */
150 | void setMinimumScale(float minimumScale);
151 |
152 | /**
153 | * Use {@link #setMediumScale(float mediumScale)} instead, this will be removed in future
154 | * release
155 | *
156 | * Sets the middle scale level. What this value represents depends on the current {@link
157 | * android.widget.ImageView.ScaleType}.
158 | *
159 | * @param midScale medium scale preset
160 | */
161 | @Deprecated
162 | void setMidScale(float midScale);
163 |
164 | /*
165 | * Sets the medium scale level. What this value represents depends on the current {@link android.widget.ImageView.ScaleType}.
166 | *
167 | * @param mediumScale medium scale preset
168 | */
169 | void setMediumScale(float mediumScale);
170 |
171 | /**
172 | * Use {@link #setMaximumScale(float maximumScale)} instead, this will be removed in future
173 | * release
174 | *
175 | * Sets the maximum scale level. What this value represents depends on the current {@link
176 | * android.widget.ImageView.ScaleType}.
177 | *
178 | * @param maxScale maximum allowed scale preset
179 | */
180 | @Deprecated
181 | void setMaxScale(float maxScale);
182 |
183 | /**
184 | * Sets the maximum scale level. What this value represents depends on the current {@link
185 | * android.widget.ImageView.ScaleType}.
186 | *
187 | * @param maximumScale maximum allowed scale preset
188 | */
189 | void setMaximumScale(float maximumScale);
190 |
191 | /**
192 | * Register a callback to be invoked when the Photo displayed by this view is long-pressed.
193 | *
194 | * @param listener - Listener to be registered.
195 | */
196 | void setOnLongClickListener(View.OnLongClickListener listener);
197 |
198 | /**
199 | * Register a callback to be invoked when the Matrix has changed for this View. An example would
200 | * be the user panning or scaling the Photo.
201 | *
202 | * @param listener - Listener to be registered.
203 | */
204 | void setOnMatrixChangeListener(PhotoViewAttacher.OnMatrixChangedListener listener);
205 |
206 | /**
207 | * Register a callback to be invoked when the Photo displayed by this View is tapped with a
208 | * single tap.
209 | *
210 | * @param listener - Listener to be registered.
211 | */
212 | void setOnPhotoTapListener(PhotoViewAttacher.OnPhotoTapListener listener);
213 |
214 | /**
215 | * Returns a listener to be invoked when the Photo displayed by this View is tapped with a
216 | * single tap.
217 | *
218 | * @return PhotoViewAttacher.OnPhotoTapListener currently set, may be null
219 | */
220 | PhotoViewAttacher.OnPhotoTapListener getOnPhotoTapListener();
221 |
222 | /**
223 | * Register a callback to be invoked when the View is tapped with a single tap.
224 | *
225 | * @param listener - Listener to be registered.
226 | */
227 | void setOnViewTapListener(PhotoViewAttacher.OnViewTapListener listener);
228 |
229 | /**
230 | * Enables rotation via PhotoView internal functions.
231 | *
232 | * @param rotationDegree - Degree to rotate PhotoView to, should be in range 0 to 360
233 | */
234 | void setRotationTo(float rotationDegree);
235 |
236 | /**
237 | * Enables rotation via PhotoView internal functions.
238 | *
239 | * @param rotationDegree - Degree to rotate PhotoView by, should be in range 0 to 360
240 | */
241 | void setRotationBy(float rotationDegree);
242 |
243 | /**
244 | * Returns a callback listener to be invoked when the View is tapped with a single tap.
245 | *
246 | * @return PhotoViewAttacher.OnViewTapListener currently set, may be null
247 | */
248 | PhotoViewAttacher.OnViewTapListener getOnViewTapListener();
249 |
250 | /**
251 | * Changes the current scale to the specified value.
252 | *
253 | * @param scale - Value to scale to
254 | */
255 | void setScale(float scale);
256 |
257 | /**
258 | * Changes the current scale to the specified value.
259 | *
260 | * @param scale - Value to scale to
261 | * @param animate - Whether to animate the scale
262 | */
263 | void setScale(float scale, boolean animate);
264 |
265 | /**
266 | * Changes the current scale to the specified value, around the given focal point.
267 | *
268 | * @param scale - Value to scale to
269 | * @param focalX - X Focus Point
270 | * @param focalY - Y Focus Point
271 | * @param animate - Whether to animate the scale
272 | */
273 | void setScale(float scale, float focalX, float focalY, boolean animate);
274 |
275 | /**
276 | * Controls how the image should be resized or moved to match the size of the ImageView. Any
277 | * scaling or panning will happen within the confines of this {@link
278 | * android.widget.ImageView.ScaleType}.
279 | *
280 | * @param scaleType - The desired scaling mode.
281 | */
282 | void setScaleType(ImageView.ScaleType scaleType);
283 |
284 | /**
285 | * Allows you to enable/disable the zoom functionality on the ImageView. When disable the
286 | * ImageView reverts to using the FIT_CENTER matrix.
287 | *
288 | * @param zoomable - Whether the zoom functionality is enabled.
289 | */
290 | void setZoomable(boolean zoomable);
291 |
292 | /**
293 | * Enables rotation via PhotoView internal functions. Name is chosen so it won't collide with
294 | * View.setRotation(float) in API since 11
295 | *
296 | * @param rotationDegree - Degree to rotate PhotoView to, should be in range 0 to 360
297 | * @deprecated use {@link #setRotationTo(float)}
298 | */
299 | void setPhotoViewRotation(float rotationDegree);
300 |
301 | /**
302 | * Extracts currently visible area to Bitmap object, if there is no image loaded yet or the
303 | * ImageView is already destroyed, returns {@code null}
304 | *
305 | * @return currently visible area as bitmap or null
306 | */
307 | Bitmap getVisibleRectangleBitmap();
308 |
309 | /**
310 | * Allows to change zoom transition speed, default value is 200 (PhotoViewAttacher.DEFAULT_ZOOM_DURATION).
311 | * Will default to 200 if provided negative value
312 | *
313 | * @param milliseconds duration of zoom interpolation
314 | */
315 | void setZoomTransitionDuration(int milliseconds);
316 |
317 | /**
318 | * Will return instance of IPhotoView (eg. PhotoViewAttacher), can be used to provide better
319 | * integration
320 | *
321 | * @return IPhotoView implementation instance if available, null if not
322 | */
323 | IPhotoView getIPhotoViewImplementation();
324 |
325 | /**
326 | * Sets custom double tap listener, to intercept default given functions. To reset behavior to
327 | * default, you can just pass in "null" or public field of PhotoViewAttacher.defaultOnDoubleTapListener
328 | *
329 | * @param newOnDoubleTapListener custom OnDoubleTapListener to be set on ImageView
330 | */
331 | public void setOnDoubleTapListener(GestureDetector.OnDoubleTapListener newOnDoubleTapListener);
332 | }
333 |
--------------------------------------------------------------------------------
/src/com/others/PhotoView.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright 2011, 2012 Chris Banes.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *******************************************************************************/
16 | package com.others;
17 |
18 | import android.content.Context;
19 | import android.graphics.Bitmap;
20 | import android.graphics.Matrix;
21 | import android.graphics.RectF;
22 | import android.graphics.drawable.Drawable;
23 | import android.net.Uri;
24 | import android.util.AttributeSet;
25 | import android.view.GestureDetector;
26 | import android.widget.ImageView;
27 |
28 |
29 | public class PhotoView extends ImageView implements IPhotoView {
30 |
31 | private final PhotoViewAttacher mAttacher;
32 |
33 | private ScaleType mPendingScaleType;
34 |
35 | public PhotoView(Context context) {
36 | this(context, null);
37 | }
38 |
39 | public PhotoView(Context context, AttributeSet attr) {
40 | this(context, attr, 0);
41 | }
42 |
43 | public PhotoView(Context context, AttributeSet attr, int defStyle) {
44 | super(context, attr, defStyle);
45 | super.setScaleType(ScaleType.MATRIX);
46 | mAttacher = new PhotoViewAttacher(this);
47 |
48 | if (null != mPendingScaleType) {
49 | setScaleType(mPendingScaleType);
50 | mPendingScaleType = null;
51 | }
52 | }
53 |
54 | /**
55 | * @deprecated use {@link #setRotationTo(float)}
56 | */
57 | @Override
58 | public void setPhotoViewRotation(float rotationDegree) {
59 | mAttacher.setRotationTo(rotationDegree);
60 | }
61 |
62 | @Override
63 | public void setRotationTo(float rotationDegree) {
64 | mAttacher.setRotationTo(rotationDegree);
65 | }
66 |
67 | @Override
68 | public void setRotationBy(float rotationDegree) {
69 | mAttacher.setRotationBy(rotationDegree);
70 | }
71 |
72 | @Override
73 | public boolean canZoom() {
74 | return mAttacher.canZoom();
75 | }
76 |
77 | @Override
78 | public RectF getDisplayRect() {
79 | return mAttacher.getDisplayRect();
80 | }
81 |
82 | @Override
83 | public Matrix getDisplayMatrix() {
84 | return mAttacher.getDrawMatrix();
85 | }
86 |
87 | @Override
88 | public boolean setDisplayMatrix(Matrix finalRectangle) {
89 | return mAttacher.setDisplayMatrix(finalRectangle);
90 | }
91 |
92 | @Override
93 | @Deprecated
94 | public float getMinScale() {
95 | return getMinimumScale();
96 | }
97 |
98 | @Override
99 | public float getMinimumScale() {
100 | return mAttacher.getMinimumScale();
101 | }
102 |
103 | @Override
104 | @Deprecated
105 | public float getMidScale() {
106 | return getMediumScale();
107 | }
108 |
109 | @Override
110 | public float getMediumScale() {
111 | return mAttacher.getMediumScale();
112 | }
113 |
114 | @Override
115 | @Deprecated
116 | public float getMaxScale() {
117 | return getMaximumScale();
118 | }
119 |
120 | @Override
121 | public float getMaximumScale() {
122 | return mAttacher.getMaximumScale();
123 | }
124 |
125 | @Override
126 | public float getScale() {
127 | return mAttacher.getScale();
128 | }
129 |
130 | @Override
131 | public ScaleType getScaleType() {
132 | return mAttacher.getScaleType();
133 | }
134 |
135 | @Override
136 | public void setAllowParentInterceptOnEdge(boolean allow) {
137 | mAttacher.setAllowParentInterceptOnEdge(allow);
138 | }
139 |
140 | @Override
141 | @Deprecated
142 | public void setMinScale(float minScale) {
143 | setMinimumScale(minScale);
144 | }
145 |
146 | @Override
147 | public void setMinimumScale(float minimumScale) {
148 | mAttacher.setMinimumScale(minimumScale);
149 | }
150 |
151 | @Override
152 | @Deprecated
153 | public void setMidScale(float midScale) {
154 | setMediumScale(midScale);
155 | }
156 |
157 | @Override
158 | public void setMediumScale(float mediumScale) {
159 | mAttacher.setMediumScale(mediumScale);
160 | }
161 |
162 | @Override
163 | @Deprecated
164 | public void setMaxScale(float maxScale) {
165 | setMaximumScale(maxScale);
166 | }
167 |
168 | @Override
169 | public void setMaximumScale(float maximumScale) {
170 | mAttacher.setMaximumScale(maximumScale);
171 | }
172 |
173 | @Override
174 | // setImageBitmap calls through to this method
175 | public void setImageDrawable(Drawable drawable) {
176 | super.setImageDrawable(drawable);
177 | if (null != mAttacher) {
178 | mAttacher.update();
179 | }
180 | }
181 |
182 | @Override
183 | public void setImageResource(int resId) {
184 | super.setImageResource(resId);
185 | if (null != mAttacher) {
186 | mAttacher.update();
187 | }
188 | }
189 |
190 | @Override
191 | public void setImageURI(Uri uri) {
192 | super.setImageURI(uri);
193 | if (null != mAttacher) {
194 | mAttacher.update();
195 | }
196 | }
197 |
198 | @Override
199 | public void setOnMatrixChangeListener(PhotoViewAttacher.OnMatrixChangedListener listener) {
200 | mAttacher.setOnMatrixChangeListener(listener);
201 | }
202 |
203 | @Override
204 | public void setOnLongClickListener(OnLongClickListener l) {
205 | mAttacher.setOnLongClickListener(l);
206 | }
207 |
208 | @Override
209 | public void setOnPhotoTapListener(PhotoViewAttacher.OnPhotoTapListener listener) {
210 | mAttacher.setOnPhotoTapListener(listener);
211 | }
212 |
213 | @Override
214 | public PhotoViewAttacher.OnPhotoTapListener getOnPhotoTapListener() {
215 | return mAttacher.getOnPhotoTapListener();
216 | }
217 |
218 | @Override
219 | public void setOnViewTapListener(PhotoViewAttacher.OnViewTapListener listener) {
220 | mAttacher.setOnViewTapListener(listener);
221 | }
222 |
223 | @Override
224 | public PhotoViewAttacher.OnViewTapListener getOnViewTapListener() {
225 | return mAttacher.getOnViewTapListener();
226 | }
227 |
228 | @Override
229 | public void setScale(float scale) {
230 | mAttacher.setScale(scale);
231 | }
232 |
233 | @Override
234 | public void setScale(float scale, boolean animate) {
235 | mAttacher.setScale(scale, animate);
236 | }
237 |
238 | @Override
239 | public void setScale(float scale, float focalX, float focalY, boolean animate) {
240 | mAttacher.setScale(scale, focalX, focalY, animate);
241 | }
242 |
243 | @Override
244 | public void setScaleType(ScaleType scaleType) {
245 | if (null != mAttacher) {
246 | mAttacher.setScaleType(scaleType);
247 | } else {
248 | mPendingScaleType = scaleType;
249 | }
250 | }
251 |
252 | @Override
253 | public void setZoomable(boolean zoomable) {
254 | mAttacher.setZoomable(zoomable);
255 | }
256 |
257 | @Override
258 | public Bitmap getVisibleRectangleBitmap() {
259 | return mAttacher.getVisibleRectangleBitmap();
260 | }
261 |
262 | @Override
263 | public void setZoomTransitionDuration(int milliseconds) {
264 | mAttacher.setZoomTransitionDuration(milliseconds);
265 | }
266 |
267 | @Override
268 | public IPhotoView getIPhotoViewImplementation() {
269 | return mAttacher;
270 | }
271 |
272 | @Override
273 | public void setOnDoubleTapListener(GestureDetector.OnDoubleTapListener newOnDoubleTapListener) {
274 | mAttacher.setOnDoubleTapListener(newOnDoubleTapListener);
275 | }
276 |
277 | @Override
278 | protected void onDetachedFromWindow() {
279 | mAttacher.cleanup();
280 | super.onDetachedFromWindow();
281 | }
282 |
283 | }
--------------------------------------------------------------------------------
/src/com/others/PhotoViewAttacher.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright 2011, 2012 Chris Banes.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *******************************************************************************/
16 | package com.others;
17 |
18 | import android.content.Context;
19 | import android.graphics.Bitmap;
20 | import android.graphics.Matrix;
21 | import android.graphics.Matrix.ScaleToFit;
22 | import android.graphics.RectF;
23 | import android.graphics.drawable.Drawable;
24 | import android.util.FloatMath;
25 | import android.util.Log;
26 | import android.view.GestureDetector;
27 | import android.view.MotionEvent;
28 | import android.view.View;
29 | import android.view.View.OnLongClickListener;
30 | import android.view.ViewParent;
31 | import android.view.ViewTreeObserver;
32 | import android.view.animation.AccelerateDecelerateInterpolator;
33 | import android.view.animation.Interpolator;
34 | import android.widget.ImageView;
35 | import android.widget.ImageView.ScaleType;
36 |
37 | import com.others.gestures.OnGestureListener;
38 | import com.others.gestures.VersionedGestureDetector;
39 | import com.others.log.LogManager;
40 | import com.others.scrollerproxy.ScrollerProxy;
41 |
42 | import java.lang.ref.WeakReference;
43 |
44 |
45 | import static android.view.MotionEvent.ACTION_CANCEL;
46 | import static android.view.MotionEvent.ACTION_DOWN;
47 | import static android.view.MotionEvent.ACTION_UP;
48 |
49 | public class PhotoViewAttacher implements IPhotoView, View.OnTouchListener,
50 | OnGestureListener,
51 | ViewTreeObserver.OnGlobalLayoutListener {
52 |
53 | private static final String LOG_TAG = "PhotoViewAttacher";
54 |
55 | // let debug flag be dynamic, but still Proguard can be used to remove from
56 | // release builds
57 | private static final boolean DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG);
58 |
59 | static final Interpolator sInterpolator = new AccelerateDecelerateInterpolator();
60 | int ZOOM_DURATION = DEFAULT_ZOOM_DURATION;
61 |
62 | static final int EDGE_NONE = -1;
63 | static final int EDGE_LEFT = 0;
64 | static final int EDGE_RIGHT = 1;
65 | static final int EDGE_BOTH = 2;
66 |
67 | private float mMinScale = DEFAULT_MIN_SCALE;
68 | private float mMidScale = DEFAULT_MID_SCALE;
69 | private float mMaxScale = DEFAULT_MAX_SCALE;
70 |
71 | private boolean mAllowParentInterceptOnEdge = true;
72 |
73 | private static void checkZoomLevels(float minZoom, float midZoom,
74 | float maxZoom) {
75 | if (minZoom >= midZoom) {
76 | throw new IllegalArgumentException(
77 | "MinZoom has to be less than MidZoom");
78 | } else if (midZoom >= maxZoom) {
79 | throw new IllegalArgumentException(
80 | "MidZoom has to be less than MaxZoom");
81 | }
82 | }
83 |
84 | /**
85 | * @return true if the ImageView exists, and it's Drawable existss
86 | */
87 | private static boolean hasDrawable(ImageView imageView) {
88 | return null != imageView && null != imageView.getDrawable();
89 | }
90 |
91 | /**
92 | * @return true if the ScaleType is supported.
93 | */
94 | private static boolean isSupportedScaleType(final ScaleType scaleType) {
95 | if (null == scaleType) {
96 | return false;
97 | }
98 |
99 | switch (scaleType) {
100 | case MATRIX:
101 | throw new IllegalArgumentException(scaleType.name()
102 | + " is not supported in PhotoView");
103 |
104 | default:
105 | return true;
106 | }
107 | }
108 |
109 | /**
110 | * Set's the ImageView's ScaleType to Matrix.
111 | */
112 | private static void setImageViewScaleTypeMatrix(ImageView imageView) {
113 | /**
114 | * PhotoView sets it's own ScaleType to Matrix, then diverts all calls
115 | * setScaleType to this.setScaleType automatically.
116 | */
117 | if (null != imageView && !(imageView instanceof IPhotoView)) {
118 | if (!ScaleType.MATRIX.equals(imageView.getScaleType())) {
119 | imageView.setScaleType(ScaleType.MATRIX);
120 | }
121 | }
122 | }
123 |
124 | private WeakReference mImageView;
125 |
126 | // Gesture Detectors
127 | private GestureDetector mGestureDetector;
128 | private com.others.gestures.GestureDetector mScaleDragDetector;
129 |
130 | // These are set so we don't keep allocating them on the heap
131 | private final Matrix mBaseMatrix = new Matrix();
132 | private final Matrix mDrawMatrix = new Matrix();
133 | private final Matrix mSuppMatrix = new Matrix();
134 | private final RectF mDisplayRect = new RectF();
135 | private final float[] mMatrixValues = new float[9];
136 |
137 | // Listeners
138 | private OnMatrixChangedListener mMatrixChangeListener;
139 | private OnPhotoTapListener mPhotoTapListener;
140 | private OnViewTapListener mViewTapListener;
141 | private OnLongClickListener mLongClickListener;
142 |
143 | private int mIvTop, mIvRight, mIvBottom, mIvLeft;
144 | private FlingRunnable mCurrentFlingRunnable;
145 | private int mScrollEdge = EDGE_BOTH;
146 |
147 | private boolean mZoomEnabled;
148 | private ScaleType mScaleType = ScaleType.FIT_CENTER;
149 |
150 | public PhotoViewAttacher(ImageView imageView) {
151 | mImageView = new WeakReference(imageView);
152 |
153 | imageView.setDrawingCacheEnabled(true);
154 | imageView.setOnTouchListener(this);
155 |
156 | ViewTreeObserver observer = imageView.getViewTreeObserver();
157 | if (null != observer)
158 | observer.addOnGlobalLayoutListener(this);
159 |
160 | // Make sure we using MATRIX Scale Type
161 | setImageViewScaleTypeMatrix(imageView);
162 |
163 | if (imageView.isInEditMode()) {
164 | return;
165 | }
166 | // Create Gesture Detectors...
167 | mScaleDragDetector = VersionedGestureDetector.newInstance(
168 | imageView.getContext(), this);
169 |
170 | mGestureDetector = new GestureDetector(imageView.getContext(),
171 | new GestureDetector.SimpleOnGestureListener() {
172 |
173 | // forward long click listener
174 | @Override
175 | public void onLongPress(MotionEvent e) {
176 | if (null != mLongClickListener) {
177 | mLongClickListener.onLongClick(getImageView());
178 | }
179 | }
180 | });
181 |
182 | mGestureDetector.setOnDoubleTapListener(new DefaultOnDoubleTapListener(this));
183 |
184 | // Finally, update the UI so that we're zoomable
185 | setZoomable(true);
186 | }
187 |
188 | @Override
189 | public void setOnDoubleTapListener(GestureDetector.OnDoubleTapListener newOnDoubleTapListener) {
190 | if (newOnDoubleTapListener != null)
191 | this.mGestureDetector.setOnDoubleTapListener(newOnDoubleTapListener);
192 | else
193 | this.mGestureDetector.setOnDoubleTapListener(new DefaultOnDoubleTapListener(this));
194 | }
195 |
196 | @Override
197 | public boolean canZoom() {
198 | return mZoomEnabled;
199 | }
200 |
201 | /**
202 | * Clean-up the resources attached to this object. This needs to be called when the ImageView is
203 | * no longer used. A good example is from {@link android.view.View#onDetachedFromWindow()} or
204 | * from {@link android.app.Activity#onDestroy()}. This is automatically called if you are using
205 | */
206 | @SuppressWarnings("deprecation")
207 | public void cleanup() {
208 | if (null == mImageView) {
209 | return; // cleanup already done
210 | }
211 |
212 | final ImageView imageView = mImageView.get();
213 |
214 | if (null != imageView) {
215 | // Remove this as a global layout listener
216 | ViewTreeObserver observer = imageView.getViewTreeObserver();
217 | if (null != observer && observer.isAlive()) {
218 | observer.removeGlobalOnLayoutListener(this);
219 | }
220 |
221 | // Remove the ImageView's reference to this
222 | imageView.setOnTouchListener(null);
223 |
224 | // make sure a pending fling runnable won't be run
225 | cancelFling();
226 | }
227 |
228 | if (null != mGestureDetector) {
229 | mGestureDetector.setOnDoubleTapListener(null);
230 | }
231 |
232 | // Clear listeners too
233 | mMatrixChangeListener = null;
234 | mPhotoTapListener = null;
235 | mViewTapListener = null;
236 |
237 | // Finally, clear ImageView
238 | mImageView = null;
239 | }
240 |
241 | @Override
242 | public RectF getDisplayRect() {
243 | checkMatrixBounds();
244 | return getDisplayRect(getDrawMatrix());
245 | }
246 |
247 | @Override
248 | public boolean setDisplayMatrix(Matrix finalMatrix) {
249 | if (finalMatrix == null)
250 | throw new IllegalArgumentException("Matrix cannot be null");
251 |
252 | ImageView imageView = getImageView();
253 | if (null == imageView)
254 | return false;
255 |
256 | if (null == imageView.getDrawable())
257 | return false;
258 |
259 | mSuppMatrix.set(finalMatrix);
260 | setImageViewMatrix(getDrawMatrix());
261 | checkMatrixBounds();
262 |
263 | return true;
264 | }
265 |
266 | /**
267 | * @deprecated use {@link #setRotationTo(float)}
268 | */
269 | @Override
270 | public void setPhotoViewRotation(float degrees) {
271 | mSuppMatrix.setRotate(degrees % 360);
272 | checkAndDisplayMatrix();
273 | }
274 |
275 | @Override
276 | public void setRotationTo(float degrees) {
277 | mSuppMatrix.setRotate(degrees % 360);
278 | checkAndDisplayMatrix();
279 | }
280 |
281 | @Override
282 | public void setRotationBy(float degrees) {
283 | mSuppMatrix.postRotate(degrees % 360);
284 | checkAndDisplayMatrix();
285 | }
286 |
287 | public ImageView getImageView() {
288 | ImageView imageView = null;
289 |
290 | if (null != mImageView) {
291 | imageView = mImageView.get();
292 | }
293 |
294 | // If we don't have an ImageView, call cleanup()
295 | if (null == imageView) {
296 | cleanup();
297 | Log.i(LOG_TAG,
298 | "ImageView no longer exists. You should not use this PhotoViewAttacher any more.");
299 | }
300 |
301 | return imageView;
302 | }
303 |
304 | @Override
305 | @Deprecated
306 | public float getMinScale() {
307 | return getMinimumScale();
308 | }
309 |
310 | @Override
311 | public float getMinimumScale() {
312 | return mMinScale;
313 | }
314 |
315 | @Override
316 | @Deprecated
317 | public float getMidScale() {
318 | return getMediumScale();
319 | }
320 |
321 | @Override
322 | public float getMediumScale() {
323 | return mMidScale;
324 | }
325 |
326 | @Override
327 | @Deprecated
328 | public float getMaxScale() {
329 | return getMaximumScale();
330 | }
331 |
332 | @Override
333 | public float getMaximumScale() {
334 | return mMaxScale;
335 | }
336 |
337 | @Override
338 | public float getScale() {
339 | return FloatMath.sqrt((float) Math.pow(getValue(mSuppMatrix, Matrix.MSCALE_X), 2) + (float) Math.pow(getValue(mSuppMatrix, Matrix.MSKEW_Y), 2));
340 | }
341 |
342 | @Override
343 | public ScaleType getScaleType() {
344 | return mScaleType;
345 | }
346 |
347 | @Override
348 | public void onDrag(float dx, float dy) {
349 | if (mScaleDragDetector.isScaling()) {
350 | return; // Do not drag if we are already scaling
351 | }
352 |
353 | if (DEBUG) {
354 | LogManager.getLogger().d(LOG_TAG,
355 | String.format("onDrag: dx: %.2f. dy: %.2f", dx, dy));
356 | }
357 |
358 | ImageView imageView = getImageView();
359 | mSuppMatrix.postTranslate(dx, dy);
360 | checkAndDisplayMatrix();
361 |
362 | /**
363 | * Here we decide whether to let the ImageView's parent to start taking
364 | * over the touch event.
365 | *
366 | * First we check whether this function is enabled. We never want the
367 | * parent to take over if we're scaling. We then check the edge we're
368 | * on, and the direction of the scroll (i.e. if we're pulling against
369 | * the edge, aka 'overscrolling', let the parent take over).
370 | */
371 | ViewParent parent = imageView.getParent();
372 | if (mAllowParentInterceptOnEdge && !mScaleDragDetector.isScaling()) {
373 | if (mScrollEdge == EDGE_BOTH
374 | || (mScrollEdge == EDGE_LEFT && dx >= 1f)
375 | || (mScrollEdge == EDGE_RIGHT && dx <= -1f)) {
376 | if (null != parent)
377 | parent.requestDisallowInterceptTouchEvent(false);
378 | }
379 | } else {
380 | if (null != parent) {
381 | parent.requestDisallowInterceptTouchEvent(true);
382 | }
383 | }
384 | }
385 |
386 | @Override
387 | public void onFling(float startX, float startY, float velocityX,
388 | float velocityY) {
389 | if (DEBUG) {
390 | LogManager.getLogger().d(
391 | LOG_TAG,
392 | "onFling. sX: " + startX + " sY: " + startY + " Vx: "
393 | + velocityX + " Vy: " + velocityY);
394 | }
395 | ImageView imageView = getImageView();
396 | mCurrentFlingRunnable = new FlingRunnable(imageView.getContext());
397 | mCurrentFlingRunnable.fling(getImageViewWidth(imageView),
398 | getImageViewHeight(imageView), (int) velocityX, (int) velocityY);
399 | imageView.post(mCurrentFlingRunnable);
400 | }
401 |
402 | @Override
403 | public void onGlobalLayout() {
404 | ImageView imageView = getImageView();
405 |
406 | if (null != imageView) {
407 | if (mZoomEnabled) {
408 | final int top = imageView.getTop();
409 | final int right = imageView.getRight();
410 | final int bottom = imageView.getBottom();
411 | final int left = imageView.getLeft();
412 |
413 | /**
414 | * We need to check whether the ImageView's bounds have changed.
415 | * This would be easier if we targeted API 11+ as we could just use
416 | * View.OnLayoutChangeListener. Instead we have to replicate the
417 | * work, keeping track of the ImageView's bounds and then checking
418 | * if the values change.
419 | */
420 | if (top != mIvTop || bottom != mIvBottom || left != mIvLeft
421 | || right != mIvRight) {
422 | // Update our base matrix, as the bounds have changed
423 | updateBaseMatrix(imageView.getDrawable());
424 |
425 | // Update values as something has changed
426 | mIvTop = top;
427 | mIvRight = right;
428 | mIvBottom = bottom;
429 | mIvLeft = left;
430 | }
431 | } else {
432 | updateBaseMatrix(imageView.getDrawable());
433 | }
434 | }
435 | }
436 |
437 | @Override
438 | public void onScale(float scaleFactor, float focusX, float focusY) {
439 | if (DEBUG) {
440 | LogManager.getLogger().d(
441 | LOG_TAG,
442 | String.format("onScale: scale: %.2f. fX: %.2f. fY: %.2f",
443 | scaleFactor, focusX, focusY));
444 | }
445 |
446 | if (getScale() < mMaxScale || scaleFactor < 1f) {
447 | mSuppMatrix.postScale(scaleFactor, scaleFactor, focusX, focusY);
448 | checkAndDisplayMatrix();
449 | }
450 | }
451 |
452 | @Override
453 | public boolean onTouch(View v, MotionEvent ev) {
454 | boolean handled = false;
455 |
456 | if (mZoomEnabled && hasDrawable((ImageView) v)) {
457 | ViewParent parent = v.getParent();
458 | switch (ev.getAction()) {
459 | case ACTION_DOWN:
460 | // First, disable the Parent from intercepting the touch
461 | // event
462 | if (null != parent)
463 | parent.requestDisallowInterceptTouchEvent(true);
464 | else
465 | Log.i(LOG_TAG, "onTouch getParent() returned null");
466 |
467 | // If we're flinging, and the user presses down, cancel
468 | // fling
469 | cancelFling();
470 | break;
471 |
472 | case ACTION_CANCEL:
473 | case ACTION_UP:
474 | // If the user has zoomed less than min scale, zoom back
475 | // to min scale
476 | if (getScale() < mMinScale) {
477 | RectF rect = getDisplayRect();
478 | if (null != rect) {
479 | v.post(new AnimatedZoomRunnable(getScale(), mMinScale,
480 | rect.centerX(), rect.centerY()));
481 | handled = true;
482 | }
483 | }
484 | break;
485 | }
486 |
487 | // Try the Scale/Drag detector
488 | if (null != mScaleDragDetector
489 | && mScaleDragDetector.onTouchEvent(ev)) {
490 | handled = true;
491 | }
492 |
493 | // Check to see if the user double tapped
494 | if (null != mGestureDetector && mGestureDetector.onTouchEvent(ev)) {
495 | handled = true;
496 | }
497 | }
498 |
499 | return handled;
500 | }
501 |
502 | @Override
503 | public void setAllowParentInterceptOnEdge(boolean allow) {
504 | mAllowParentInterceptOnEdge = allow;
505 | }
506 |
507 | @Override
508 | @Deprecated
509 | public void setMinScale(float minScale) {
510 | setMinimumScale(minScale);
511 | }
512 |
513 | @Override
514 | public void setMinimumScale(float minimumScale) {
515 | checkZoomLevels(minimumScale, mMidScale, mMaxScale);
516 | mMinScale = minimumScale;
517 | }
518 |
519 | @Override
520 | @Deprecated
521 | public void setMidScale(float midScale) {
522 | setMediumScale(midScale);
523 | }
524 |
525 | @Override
526 | public void setMediumScale(float mediumScale) {
527 | checkZoomLevels(mMinScale, mediumScale, mMaxScale);
528 | mMidScale = mediumScale;
529 | }
530 |
531 | @Override
532 | @Deprecated
533 | public void setMaxScale(float maxScale) {
534 | setMaximumScale(maxScale);
535 | }
536 |
537 | @Override
538 | public void setMaximumScale(float maximumScale) {
539 | checkZoomLevels(mMinScale, mMidScale, maximumScale);
540 | mMaxScale = maximumScale;
541 | }
542 |
543 | @Override
544 | public void setOnLongClickListener(OnLongClickListener listener) {
545 | mLongClickListener = listener;
546 | }
547 |
548 | @Override
549 | public void setOnMatrixChangeListener(OnMatrixChangedListener listener) {
550 | mMatrixChangeListener = listener;
551 | }
552 |
553 | @Override
554 | public void setOnPhotoTapListener(OnPhotoTapListener listener) {
555 | mPhotoTapListener = listener;
556 | }
557 |
558 | @Override
559 | public OnPhotoTapListener getOnPhotoTapListener() {
560 | return mPhotoTapListener;
561 | }
562 |
563 | @Override
564 | public void setOnViewTapListener(OnViewTapListener listener) {
565 | mViewTapListener = listener;
566 | }
567 |
568 | @Override
569 | public OnViewTapListener getOnViewTapListener() {
570 | return mViewTapListener;
571 | }
572 |
573 | @Override
574 | public void setScale(float scale) {
575 | setScale(scale, false);
576 | }
577 |
578 | @Override
579 | public void setScale(float scale, boolean animate) {
580 | ImageView imageView = getImageView();
581 |
582 | if (null != imageView) {
583 | setScale(scale,
584 | (imageView.getRight()) / 2,
585 | (imageView.getBottom()) / 2,
586 | animate);
587 | }
588 | }
589 |
590 | @Override
591 | public void setScale(float scale, float focalX, float focalY,
592 | boolean animate) {
593 | ImageView imageView = getImageView();
594 |
595 | if (null != imageView) {
596 | // Check to see if the scale is within bounds
597 | if (scale < mMinScale || scale > mMaxScale) {
598 | LogManager
599 | .getLogger()
600 | .i(LOG_TAG,
601 | "Scale must be within the range of minScale and maxScale");
602 | return;
603 | }
604 |
605 | if (animate) {
606 | imageView.post(new AnimatedZoomRunnable(getScale(), scale,
607 | focalX, focalY));
608 | } else {
609 | mSuppMatrix.setScale(scale, scale, focalX, focalY);
610 | checkAndDisplayMatrix();
611 | }
612 | }
613 | }
614 |
615 | @Override
616 | public void setScaleType(ScaleType scaleType) {
617 | if (isSupportedScaleType(scaleType) && scaleType != mScaleType) {
618 | mScaleType = scaleType;
619 |
620 | // Finally update
621 | update();
622 | }
623 | }
624 |
625 | @Override
626 | public void setZoomable(boolean zoomable) {
627 | mZoomEnabled = zoomable;
628 | update();
629 | }
630 |
631 | public void update() {
632 | ImageView imageView = getImageView();
633 |
634 | if (null != imageView) {
635 | if (mZoomEnabled) {
636 | // Make sure we using MATRIX Scale Type
637 | setImageViewScaleTypeMatrix(imageView);
638 |
639 | // Update the base matrix using the current drawable
640 | updateBaseMatrix(imageView.getDrawable());
641 | } else {
642 | // Reset the Matrix...
643 | resetMatrix();
644 | }
645 | }
646 | }
647 |
648 | @Override
649 | public Matrix getDisplayMatrix() {
650 | return new Matrix(getDrawMatrix());
651 | }
652 |
653 | public Matrix getDrawMatrix() {
654 | mDrawMatrix.set(mBaseMatrix);
655 | mDrawMatrix.postConcat(mSuppMatrix);
656 | return mDrawMatrix;
657 | }
658 |
659 | private void cancelFling() {
660 | if (null != mCurrentFlingRunnable) {
661 | mCurrentFlingRunnable.cancelFling();
662 | mCurrentFlingRunnable = null;
663 | }
664 | }
665 |
666 | /**
667 | * Helper method that simply checks the Matrix, and then displays the result
668 | */
669 | private void checkAndDisplayMatrix() {
670 | if (checkMatrixBounds()) {
671 | setImageViewMatrix(getDrawMatrix());
672 | }
673 | }
674 |
675 | private void checkImageViewScaleType() {
676 | ImageView imageView = getImageView();
677 |
678 | /**
679 | * PhotoView's getScaleType() will just divert to this.getScaleType() so
680 | * only call if we're not attached to a PhotoView.
681 | */
682 | if (null != imageView && !(imageView instanceof IPhotoView)) {
683 | if (!ScaleType.MATRIX.equals(imageView.getScaleType())) {
684 | throw new IllegalStateException(
685 | "The ImageView's ScaleType has been changed since attaching a PhotoViewAttacher");
686 | }
687 | }
688 | }
689 |
690 | private boolean checkMatrixBounds() {
691 | final ImageView imageView = getImageView();
692 | if (null == imageView) {
693 | return false;
694 | }
695 |
696 | final RectF rect = getDisplayRect(getDrawMatrix());
697 | if (null == rect) {
698 | return false;
699 | }
700 |
701 | final float height = rect.height(), width = rect.width();
702 | float deltaX = 0, deltaY = 0;
703 |
704 | final int viewHeight = getImageViewHeight(imageView);
705 | if (height <= viewHeight) {
706 | switch (mScaleType) {
707 | case FIT_START:
708 | deltaY = -rect.top;
709 | break;
710 | case FIT_END:
711 | deltaY = viewHeight - height - rect.top;
712 | break;
713 | default:
714 | deltaY = (viewHeight - height) / 2 - rect.top;
715 | break;
716 | }
717 | } else if (rect.top > 0) {
718 | deltaY = -rect.top;
719 | } else if (rect.bottom < viewHeight) {
720 | deltaY = viewHeight - rect.bottom;
721 | }
722 |
723 | final int viewWidth = getImageViewWidth(imageView);
724 | if (width <= viewWidth) {
725 | switch (mScaleType) {
726 | case FIT_START:
727 | deltaX = -rect.left;
728 | break;
729 | case FIT_END:
730 | deltaX = viewWidth - width - rect.left;
731 | break;
732 | default:
733 | deltaX = (viewWidth - width) / 2 - rect.left;
734 | break;
735 | }
736 | mScrollEdge = EDGE_BOTH;
737 | } else if (rect.left > 0) {
738 | mScrollEdge = EDGE_LEFT;
739 | deltaX = -rect.left;
740 | } else if (rect.right < viewWidth) {
741 | deltaX = viewWidth - rect.right;
742 | mScrollEdge = EDGE_RIGHT;
743 | } else {
744 | mScrollEdge = EDGE_NONE;
745 | }
746 |
747 | // Finally actually translate the matrix
748 | mSuppMatrix.postTranslate(deltaX, deltaY);
749 | return true;
750 | }
751 |
752 | /**
753 | * Helper method that maps the supplied Matrix to the current Drawable
754 | *
755 | * @param matrix - Matrix to map Drawable against
756 | * @return RectF - Displayed Rectangle
757 | */
758 | private RectF getDisplayRect(Matrix matrix) {
759 | ImageView imageView = getImageView();
760 |
761 | if (null != imageView) {
762 | Drawable d = imageView.getDrawable();
763 | if (null != d) {
764 | mDisplayRect.set(0, 0, d.getIntrinsicWidth(),
765 | d.getIntrinsicHeight());
766 | matrix.mapRect(mDisplayRect);
767 | return mDisplayRect;
768 | }
769 | }
770 | return null;
771 | }
772 |
773 | public Bitmap getVisibleRectangleBitmap() {
774 | ImageView imageView = getImageView();
775 | return imageView == null ? null : imageView.getDrawingCache();
776 | }
777 |
778 | @Override
779 | public void setZoomTransitionDuration(int milliseconds) {
780 | if (milliseconds < 0)
781 | milliseconds = DEFAULT_ZOOM_DURATION;
782 | this.ZOOM_DURATION = milliseconds;
783 | }
784 |
785 | @Override
786 | public IPhotoView getIPhotoViewImplementation() {
787 | return this;
788 | }
789 |
790 | /**
791 | * Helper method that 'unpacks' a Matrix and returns the required value
792 | *
793 | * @param matrix - Matrix to unpack
794 | * @param whichValue - Which value from Matrix.M* to return
795 | * @return float - returned value
796 | */
797 | private float getValue(Matrix matrix, int whichValue) {
798 | matrix.getValues(mMatrixValues);
799 | return mMatrixValues[whichValue];
800 | }
801 |
802 | /**
803 | * Resets the Matrix back to FIT_CENTER, and then displays it.s
804 | */
805 | private void resetMatrix() {
806 | mSuppMatrix.reset();
807 | setImageViewMatrix(getDrawMatrix());
808 | checkMatrixBounds();
809 | }
810 |
811 | private void setImageViewMatrix(Matrix matrix) {
812 | ImageView imageView = getImageView();
813 | if (null != imageView) {
814 |
815 | checkImageViewScaleType();
816 | imageView.setImageMatrix(matrix);
817 |
818 | // Call MatrixChangedListener if needed
819 | if (null != mMatrixChangeListener) {
820 | RectF displayRect = getDisplayRect(matrix);
821 | if (null != displayRect) {
822 | mMatrixChangeListener.onMatrixChanged(displayRect);
823 | }
824 | }
825 | }
826 | }
827 |
828 | /**
829 | * Calculate Matrix for FIT_CENTER
830 | *
831 | * @param d - Drawable being displayed
832 | */
833 | private void updateBaseMatrix(Drawable d) {
834 | ImageView imageView = getImageView();
835 | if (null == imageView || null == d) {
836 | return;
837 | }
838 |
839 | final float viewWidth = getImageViewWidth(imageView);
840 | final float viewHeight = getImageViewHeight(imageView);
841 | final int drawableWidth = d.getIntrinsicWidth();
842 | final int drawableHeight = d.getIntrinsicHeight();
843 |
844 | mBaseMatrix.reset();
845 |
846 | final float widthScale = viewWidth / drawableWidth;
847 | final float heightScale = viewHeight / drawableHeight;
848 |
849 | if (mScaleType == ScaleType.CENTER) {
850 | mBaseMatrix.postTranslate((viewWidth - drawableWidth) / 2F,
851 | (viewHeight - drawableHeight) / 2F);
852 |
853 | } else if (mScaleType == ScaleType.CENTER_CROP) {
854 | float scale = Math.max(widthScale, heightScale);
855 | mBaseMatrix.postScale(scale, scale);
856 | mBaseMatrix.postTranslate((viewWidth - drawableWidth * scale) / 2F,
857 | (viewHeight - drawableHeight * scale) / 2F);
858 |
859 | } else if (mScaleType == ScaleType.CENTER_INSIDE) {
860 | float scale = Math.min(1.0f, Math.min(widthScale, heightScale));
861 | mBaseMatrix.postScale(scale, scale);
862 | mBaseMatrix.postTranslate((viewWidth - drawableWidth * scale) / 2F,
863 | (viewHeight - drawableHeight * scale) / 2F);
864 |
865 | } else {
866 | RectF mTempSrc = new RectF(0, 0, drawableWidth, drawableHeight);
867 | RectF mTempDst = new RectF(0, 0, viewWidth, viewHeight);
868 |
869 | switch (mScaleType) {
870 | case FIT_CENTER:
871 | mBaseMatrix
872 | .setRectToRect(mTempSrc, mTempDst, ScaleToFit.CENTER);
873 | break;
874 |
875 | case FIT_START:
876 | mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.START);
877 | break;
878 |
879 | case FIT_END:
880 | mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.END);
881 | break;
882 |
883 | case FIT_XY:
884 | mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.FILL);
885 | break;
886 |
887 | default:
888 | break;
889 | }
890 | }
891 |
892 | resetMatrix();
893 | }
894 |
895 | private int getImageViewWidth(ImageView imageView) {
896 | if (null == imageView)
897 | return 0;
898 | return imageView.getWidth() - imageView.getPaddingLeft() - imageView.getPaddingRight();
899 | }
900 |
901 | private int getImageViewHeight(ImageView imageView) {
902 | if (null == imageView)
903 | return 0;
904 | return imageView.getHeight() - imageView.getPaddingTop() - imageView.getPaddingBottom();
905 | }
906 |
907 | /**
908 | * Interface definition for a callback to be invoked when the internal Matrix has changed for
909 | * this View.
910 | *
911 | * @author Chris Banes
912 | */
913 | public static interface OnMatrixChangedListener {
914 | /**
915 | * Callback for when the Matrix displaying the Drawable has changed. This could be because
916 | * the View's bounds have changed, or the user has zoomed.
917 | *
918 | * @param rect - Rectangle displaying the Drawable's new bounds.
919 | */
920 | void onMatrixChanged(RectF rect);
921 | }
922 |
923 | /**
924 | * Interface definition for a callback to be invoked when the Photo is tapped with a single
925 | * tap.
926 | *
927 | * @author Chris Banes
928 | */
929 | public static interface OnPhotoTapListener {
930 |
931 | /**
932 | * A callback to receive where the user taps on a photo. You will only receive a callback if
933 | * the user taps on the actual photo, tapping on 'whitespace' will be ignored.
934 | *
935 | * @param view - View the user tapped.
936 | * @param x - where the user tapped from the of the Drawable, as percentage of the
937 | * Drawable width.
938 | * @param y - where the user tapped from the top of the Drawable, as percentage of the
939 | * Drawable height.
940 | */
941 | void onPhotoTap(View view, float x, float y);
942 | }
943 |
944 | /**
945 | * Interface definition for a callback to be invoked when the ImageView is tapped with a single
946 | * tap.
947 | *
948 | * @author Chris Banes
949 | */
950 | public static interface OnViewTapListener {
951 |
952 | /**
953 | * A callback to receive where the user taps on a ImageView. You will receive a callback if
954 | * the user taps anywhere on the view, tapping on 'whitespace' will not be ignored.
955 | *
956 | * @param view - View the user tapped.
957 | * @param x - where the user tapped from the left of the View.
958 | * @param y - where the user tapped from the top of the View.
959 | */
960 | void onViewTap(View view, float x, float y);
961 | }
962 |
963 | private class AnimatedZoomRunnable implements Runnable {
964 |
965 | private final float mFocalX, mFocalY;
966 | private final long mStartTime;
967 | private final float mZoomStart, mZoomEnd;
968 |
969 | public AnimatedZoomRunnable(final float currentZoom, final float targetZoom,
970 | final float focalX, final float focalY) {
971 | mFocalX = focalX;
972 | mFocalY = focalY;
973 | mStartTime = System.currentTimeMillis();
974 | mZoomStart = currentZoom;
975 | mZoomEnd = targetZoom;
976 | }
977 |
978 | @Override
979 | public void run() {
980 | ImageView imageView = getImageView();
981 | if (imageView == null) {
982 | return;
983 | }
984 |
985 | float t = interpolate();
986 | float scale = mZoomStart + t * (mZoomEnd - mZoomStart);
987 | float deltaScale = scale / getScale();
988 |
989 | mSuppMatrix.postScale(deltaScale, deltaScale, mFocalX, mFocalY);
990 | checkAndDisplayMatrix();
991 |
992 | // We haven't hit our target scale yet, so post ourselves again
993 | if (t < 1f) {
994 | Compat.postOnAnimation(imageView, this);
995 | }
996 | }
997 |
998 | private float interpolate() {
999 | float t = 1f * (System.currentTimeMillis() - mStartTime) / ZOOM_DURATION;
1000 | t = Math.min(1f, t);
1001 | t = sInterpolator.getInterpolation(t);
1002 | return t;
1003 | }
1004 | }
1005 |
1006 | private class FlingRunnable implements Runnable {
1007 |
1008 | private final ScrollerProxy mScroller;
1009 | private int mCurrentX, mCurrentY;
1010 |
1011 | public FlingRunnable(Context context) {
1012 | mScroller = ScrollerProxy.getScroller(context);
1013 | }
1014 |
1015 | public void cancelFling() {
1016 | if (DEBUG) {
1017 | LogManager.getLogger().d(LOG_TAG, "Cancel Fling");
1018 | }
1019 | mScroller.forceFinished(true);
1020 | }
1021 |
1022 | public void fling(int viewWidth, int viewHeight, int velocityX,
1023 | int velocityY) {
1024 | final RectF rect = getDisplayRect();
1025 | if (null == rect) {
1026 | return;
1027 | }
1028 |
1029 | final int startX = Math.round(-rect.left);
1030 | final int minX, maxX, minY, maxY;
1031 |
1032 | if (viewWidth < rect.width()) {
1033 | minX = 0;
1034 | maxX = Math.round(rect.width() - viewWidth);
1035 | } else {
1036 | minX = maxX = startX;
1037 | }
1038 |
1039 | final int startY = Math.round(-rect.top);
1040 | if (viewHeight < rect.height()) {
1041 | minY = 0;
1042 | maxY = Math.round(rect.height() - viewHeight);
1043 | } else {
1044 | minY = maxY = startY;
1045 | }
1046 |
1047 | mCurrentX = startX;
1048 | mCurrentY = startY;
1049 |
1050 | if (DEBUG) {
1051 | LogManager.getLogger().d(
1052 | LOG_TAG,
1053 | "fling. StartX:" + startX + " StartY:" + startY
1054 | + " MaxX:" + maxX + " MaxY:" + maxY);
1055 | }
1056 |
1057 | // If we actually can move, fling the scroller
1058 | if (startX != maxX || startY != maxY) {
1059 | mScroller.fling(startX, startY, velocityX, velocityY, minX,
1060 | maxX, minY, maxY, 0, 0);
1061 | }
1062 | }
1063 |
1064 | @Override
1065 | public void run() {
1066 | if (mScroller.isFinished()) {
1067 | return; // remaining post that should not be handled
1068 | }
1069 |
1070 | ImageView imageView = getImageView();
1071 | if (null != imageView && mScroller.computeScrollOffset()) {
1072 |
1073 | final int newX = mScroller.getCurrX();
1074 | final int newY = mScroller.getCurrY();
1075 |
1076 | if (DEBUG) {
1077 | LogManager.getLogger().d(
1078 | LOG_TAG,
1079 | "fling run(). CurrentX:" + mCurrentX + " CurrentY:"
1080 | + mCurrentY + " NewX:" + newX + " NewY:"
1081 | + newY);
1082 | }
1083 |
1084 | mSuppMatrix.postTranslate(mCurrentX - newX, mCurrentY - newY);
1085 | setImageViewMatrix(getDrawMatrix());
1086 |
1087 | mCurrentX = newX;
1088 | mCurrentY = newY;
1089 |
1090 | // Post On animation
1091 | Compat.postOnAnimation(imageView, this);
1092 | }
1093 | }
1094 | }
1095 | }
1096 |
--------------------------------------------------------------------------------
/src/com/others/gestures/CupcakeGestureDetector.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright 2011, 2012 Chris Banes.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *******************************************************************************/
16 | package com.others.gestures;
17 |
18 | import android.content.Context;
19 | import android.util.FloatMath;
20 | import android.util.Log;
21 | import android.view.MotionEvent;
22 | import android.view.VelocityTracker;
23 | import android.view.ViewConfiguration;
24 |
25 | public class CupcakeGestureDetector implements GestureDetector {
26 |
27 | protected OnGestureListener mListener;
28 | private static final String LOG_TAG = "CupcakeGestureDetector";
29 | float mLastTouchX;
30 | float mLastTouchY;
31 | final float mTouchSlop;
32 | final float mMinimumVelocity;
33 |
34 | @Override
35 | public void setOnGestureListener(OnGestureListener listener) {
36 | this.mListener = listener;
37 | }
38 |
39 | public CupcakeGestureDetector(Context context) {
40 | final ViewConfiguration configuration = ViewConfiguration
41 | .get(context);
42 | mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
43 | mTouchSlop = configuration.getScaledTouchSlop();
44 | }
45 |
46 | private VelocityTracker mVelocityTracker;
47 | private boolean mIsDragging;
48 |
49 | float getActiveX(MotionEvent ev) {
50 | return ev.getX();
51 | }
52 |
53 | float getActiveY(MotionEvent ev) {
54 | return ev.getY();
55 | }
56 |
57 | public boolean isScaling() {
58 | return false;
59 | }
60 |
61 | @Override
62 | public boolean onTouchEvent(MotionEvent ev) {
63 | switch (ev.getAction()) {
64 | case MotionEvent.ACTION_DOWN: {
65 | mVelocityTracker = VelocityTracker.obtain();
66 | if (null != mVelocityTracker) {
67 | mVelocityTracker.addMovement(ev);
68 | } else {
69 | Log.i(LOG_TAG, "Velocity tracker is null");
70 | }
71 |
72 | mLastTouchX = getActiveX(ev);
73 | mLastTouchY = getActiveY(ev);
74 | mIsDragging = false;
75 | break;
76 | }
77 |
78 | case MotionEvent.ACTION_MOVE: {
79 | final float x = getActiveX(ev);
80 | final float y = getActiveY(ev);
81 | final float dx = x - mLastTouchX, dy = y - mLastTouchY;
82 |
83 | if (!mIsDragging) {
84 | // Use Pythagoras to see if drag length is larger than
85 | // touch slop
86 | mIsDragging = FloatMath.sqrt((dx * dx) + (dy * dy)) >= mTouchSlop;
87 | }
88 |
89 | if (mIsDragging) {
90 | mListener.onDrag(dx, dy);
91 | mLastTouchX = x;
92 | mLastTouchY = y;
93 |
94 | if (null != mVelocityTracker) {
95 | mVelocityTracker.addMovement(ev);
96 | }
97 | }
98 | break;
99 | }
100 |
101 | case MotionEvent.ACTION_CANCEL: {
102 | // Recycle Velocity Tracker
103 | if (null != mVelocityTracker) {
104 | mVelocityTracker.recycle();
105 | mVelocityTracker = null;
106 | }
107 | break;
108 | }
109 |
110 | case MotionEvent.ACTION_UP: {
111 | if (mIsDragging) {
112 | if (null != mVelocityTracker) {
113 | mLastTouchX = getActiveX(ev);
114 | mLastTouchY = getActiveY(ev);
115 |
116 | // Compute velocity within the last 1000ms
117 | mVelocityTracker.addMovement(ev);
118 | mVelocityTracker.computeCurrentVelocity(1000);
119 |
120 | final float vX = mVelocityTracker.getXVelocity(), vY = mVelocityTracker
121 | .getYVelocity();
122 |
123 | // If the velocity is greater than minVelocity, call
124 | // listener
125 | if (Math.max(Math.abs(vX), Math.abs(vY)) >= mMinimumVelocity) {
126 | mListener.onFling(mLastTouchX, mLastTouchY, -vX,
127 | -vY);
128 | }
129 | }
130 | }
131 |
132 | // Recycle Velocity Tracker
133 | if (null != mVelocityTracker) {
134 | mVelocityTracker.recycle();
135 | mVelocityTracker = null;
136 | }
137 | break;
138 | }
139 | }
140 |
141 | return true;
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/src/com/others/gestures/EclairGestureDetector.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright 2011, 2012 Chris Banes.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *******************************************************************************/
16 | package com.others.gestures;
17 |
18 | import android.annotation.TargetApi;
19 | import android.content.Context;
20 | import android.view.MotionEvent;
21 |
22 | import com.others.Compat;
23 |
24 |
25 | @TargetApi(5)
26 | public class EclairGestureDetector extends CupcakeGestureDetector {
27 |
28 | private static final int INVALID_POINTER_ID = -1;
29 | private int mActivePointerId = INVALID_POINTER_ID;
30 | private int mActivePointerIndex = 0;
31 |
32 | public EclairGestureDetector(Context context) {
33 | super(context);
34 | }
35 |
36 | @Override
37 | float getActiveX(MotionEvent ev) {
38 | try {
39 | return ev.getX(mActivePointerIndex);
40 | } catch (Exception e) {
41 | return ev.getX();
42 | }
43 | }
44 |
45 | @Override
46 | float getActiveY(MotionEvent ev) {
47 | try {
48 | return ev.getY(mActivePointerIndex);
49 | } catch (Exception e) {
50 | return ev.getY();
51 | }
52 | }
53 |
54 | @Override
55 | public boolean onTouchEvent(MotionEvent ev) {
56 | final int action = ev.getAction();
57 | switch (action & MotionEvent.ACTION_MASK) {
58 | case MotionEvent.ACTION_DOWN:
59 | mActivePointerId = ev.getPointerId(0);
60 | break;
61 | case MotionEvent.ACTION_CANCEL:
62 | case MotionEvent.ACTION_UP:
63 | mActivePointerId = INVALID_POINTER_ID;
64 | break;
65 | case MotionEvent.ACTION_POINTER_UP:
66 | // Ignore deprecation, ACTION_POINTER_ID_MASK and
67 | // ACTION_POINTER_ID_SHIFT has same value and are deprecated
68 | // You can have either deprecation or lint target api warning
69 | final int pointerIndex = Compat.getPointerIndex(ev.getAction());
70 | final int pointerId = ev.getPointerId(pointerIndex);
71 | if (pointerId == mActivePointerId) {
72 | // This was our active pointer going up. Choose a new
73 | // active pointer and adjust accordingly.
74 | final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
75 | mActivePointerId = ev.getPointerId(newPointerIndex);
76 | mLastTouchX = ev.getX(newPointerIndex);
77 | mLastTouchY = ev.getY(newPointerIndex);
78 | }
79 | break;
80 | }
81 |
82 | mActivePointerIndex = ev
83 | .findPointerIndex(mActivePointerId != INVALID_POINTER_ID ? mActivePointerId
84 | : 0);
85 | return super.onTouchEvent(ev);
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/com/others/gestures/FroyoGestureDetector.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright 2011, 2012 Chris Banes.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *******************************************************************************/
16 | package com.others.gestures;
17 |
18 | import android.annotation.TargetApi;
19 | import android.content.Context;
20 | import android.view.MotionEvent;
21 | import android.view.ScaleGestureDetector;
22 |
23 | @TargetApi(8)
24 | public class FroyoGestureDetector extends EclairGestureDetector {
25 |
26 | protected final ScaleGestureDetector mDetector;
27 |
28 | public FroyoGestureDetector(Context context) {
29 | super(context);
30 | ScaleGestureDetector.OnScaleGestureListener mScaleListener = new ScaleGestureDetector.OnScaleGestureListener() {
31 |
32 | @Override
33 | public boolean onScale(ScaleGestureDetector detector) {
34 | float scaleFactor = detector.getScaleFactor();
35 |
36 | if (Float.isNaN(scaleFactor) || Float.isInfinite(scaleFactor))
37 | return false;
38 |
39 | mListener.onScale(scaleFactor,
40 | detector.getFocusX(), detector.getFocusY());
41 | return true;
42 | }
43 |
44 | @Override
45 | public boolean onScaleBegin(ScaleGestureDetector detector) {
46 | return true;
47 | }
48 |
49 | @Override
50 | public void onScaleEnd(ScaleGestureDetector detector) {
51 | // NO-OP
52 | }
53 | };
54 | mDetector = new ScaleGestureDetector(context, mScaleListener);
55 | }
56 |
57 | @Override
58 | public boolean isScaling() {
59 | return mDetector.isInProgress();
60 | }
61 |
62 | @Override
63 | public boolean onTouchEvent(MotionEvent ev) {
64 | mDetector.onTouchEvent(ev);
65 | return super.onTouchEvent(ev);
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/src/com/others/gestures/GestureDetector.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright 2011, 2012 Chris Banes.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *******************************************************************************/
16 | package com.others.gestures;
17 |
18 | import android.view.MotionEvent;
19 |
20 | public interface GestureDetector {
21 |
22 | public boolean onTouchEvent(MotionEvent ev);
23 |
24 | public boolean isScaling();
25 |
26 | public void setOnGestureListener(OnGestureListener listener);
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/src/com/others/gestures/OnGestureListener.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright 2011, 2012 Chris Banes.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *******************************************************************************/
16 | package com.others.gestures;
17 |
18 | public interface OnGestureListener {
19 |
20 | public void onDrag(float dx, float dy);
21 |
22 | public void onFling(float startX, float startY, float velocityX,
23 | float velocityY);
24 |
25 | public void onScale(float scaleFactor, float focusX, float focusY);
26 |
27 | }
--------------------------------------------------------------------------------
/src/com/others/gestures/VersionedGestureDetector.java:
--------------------------------------------------------------------------------
1 | package com.others.gestures;
2 |
3 | /*******************************************************************************
4 | * Copyright 2011, 2012 Chris Banes.
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | *******************************************************************************/
18 |
19 | import android.content.Context;
20 | import android.os.Build;
21 |
22 | import com.others.gestures.CupcakeGestureDetector;
23 | import com.others.gestures.EclairGestureDetector;
24 | import com.others.gestures.FroyoGestureDetector;
25 | import com.others.gestures.GestureDetector;
26 | import com.others.gestures.OnGestureListener;
27 |
28 | public final class VersionedGestureDetector {
29 |
30 | public static GestureDetector newInstance(Context context,
31 | OnGestureListener listener) {
32 | final int sdkVersion = Build.VERSION.SDK_INT;
33 | GestureDetector detector;
34 |
35 | if (sdkVersion < Build.VERSION_CODES.ECLAIR) {
36 | detector = new CupcakeGestureDetector(context);
37 | } else if (sdkVersion < Build.VERSION_CODES.FROYO) {
38 | detector = new EclairGestureDetector(context);
39 | } else {
40 | detector = new FroyoGestureDetector(context);
41 | }
42 |
43 | detector.setOnGestureListener(listener);
44 |
45 | return detector;
46 | }
47 |
48 | }
--------------------------------------------------------------------------------
/src/com/others/log/LogManager.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright 2011, 2012 Chris Banes.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *******************************************************************************/
16 | package com.others.log;
17 |
18 | import android.util.Log;
19 |
20 | /**
21 | * class that holds the {@link Logger} for this library, defaults to {@link LoggerDefault} to send logs to android {@link Log}
22 | */
23 | public final class LogManager {
24 |
25 | private static Logger logger = new LoggerDefault();
26 |
27 | public static void setLogger(Logger newLogger) {
28 | logger = newLogger;
29 | }
30 |
31 | public static Logger getLogger() {
32 | return logger;
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/src/com/others/log/Logger.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright 2011, 2012 Chris Banes.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *******************************************************************************/
16 | package com.others.log;
17 |
18 | /**
19 | * interface for a logger class to replace the static calls to {@link android.util.Log}
20 | */
21 | public interface Logger {
22 | /**
23 | * Send a {@link android.util.Log#VERBOSE} log message.
24 | *
25 | * @param tag Used to identify the source of a log message. It usually identifies
26 | * the class or activity where the log call occurs.
27 | * @param msg The message you would like logged.
28 | */
29 | int v(String tag, String msg);
30 |
31 | /**
32 | * Send a {@link android.util.Log#VERBOSE} log message and log the exception.
33 | *
34 | * @param tag Used to identify the source of a log message. It usually identifies
35 | * the class or activity where the log call occurs.
36 | * @param msg The message you would like logged.
37 | * @param tr An exception to log
38 | */
39 | int v(String tag, String msg, Throwable tr);
40 |
41 | /**
42 | * Send a {@link android.util.Log#DEBUG} log message.
43 | *
44 | * @param tag Used to identify the source of a log message. It usually identifies
45 | * the class or activity where the log call occurs.
46 | * @param msg The message you would like logged.
47 | */
48 | int d(String tag, String msg);
49 |
50 | /**
51 | * Send a {@link android.util.Log#DEBUG} log message and log the exception.
52 | *
53 | * @param tag Used to identify the source of a log message. It usually identifies
54 | * the class or activity where the log call occurs.
55 | * @param msg The message you would like logged.
56 | * @param tr An exception to log
57 | */
58 | int d(String tag, String msg, Throwable tr);
59 |
60 | /**
61 | * Send an {@link android.util.Log#INFO} log message.
62 | *
63 | * @param tag Used to identify the source of a log message. It usually identifies
64 | * the class or activity where the log call occurs.
65 | * @param msg The message you would like logged.
66 | */
67 | int i(String tag, String msg);
68 |
69 | /**
70 | * Send a {@link android.util.Log#INFO} log message and log the exception.
71 | *
72 | * @param tag Used to identify the source of a log message. It usually identifies
73 | * the class or activity where the log call occurs.
74 | * @param msg The message you would like logged.
75 | * @param tr An exception to log
76 | */
77 | int i(String tag, String msg, Throwable tr);
78 |
79 | /**
80 | * Send a {@link android.util.Log#WARN} log message.
81 | *
82 | * @param tag Used to identify the source of a log message. It usually identifies
83 | * the class or activity where the log call occurs.
84 | * @param msg The message you would like logged.
85 | */
86 | int w(String tag, String msg);
87 |
88 | /**
89 | * Send a {@link android.util.Log#WARN} log message and log the exception.
90 | *
91 | * @param tag Used to identify the source of a log message. It usually identifies
92 | * the class or activity where the log call occurs.
93 | * @param msg The message you would like logged.
94 | * @param tr An exception to log
95 | */
96 | int w(String tag, String msg, Throwable tr);
97 |
98 | /**
99 | * Send an {@link android.util.Log#ERROR} log message.
100 | *
101 | * @param tag Used to identify the source of a log message. It usually identifies
102 | * the class or activity where the log call occurs.
103 | * @param msg The message you would like logged.
104 | */
105 | int e(String tag, String msg);
106 |
107 | /**
108 | * Send a {@link android.util.Log#ERROR} log message and log the exception.
109 | *
110 | * @param tag Used to identify the source of a log message. It usually identifies
111 | * the class or activity where the log call occurs.
112 | * @param msg The message you would like logged.
113 | * @param tr An exception to log
114 | */
115 | int e(String tag, String msg, Throwable tr);
116 | }
117 |
--------------------------------------------------------------------------------
/src/com/others/log/LoggerDefault.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright 2011, 2012 Chris Banes.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *******************************************************************************/
16 | package com.others.log;
17 |
18 | import android.util.Log;
19 |
20 | /**
21 | * Helper class to redirect {@link LogManager#logger} to {@link Log}
22 | */
23 | public class LoggerDefault implements Logger {
24 |
25 | @Override
26 | public int v(String tag, String msg) {
27 | return Log.v(tag, msg);
28 | }
29 |
30 | @Override
31 | public int v(String tag, String msg, Throwable tr) {
32 | return Log.v(tag, msg, tr);
33 | }
34 |
35 | @Override
36 | public int d(String tag, String msg) {
37 | return Log.d(tag, msg);
38 | }
39 |
40 | @Override
41 | public int d(String tag, String msg, Throwable tr) {
42 | return Log.d(tag, msg, tr);
43 | }
44 |
45 | @Override
46 | public int i(String tag, String msg) {
47 | return Log.i(tag, msg);
48 | }
49 |
50 | @Override
51 | public int i(String tag, String msg, Throwable tr) {
52 | return Log.i(tag, msg, tr);
53 | }
54 |
55 | @Override
56 | public int w(String tag, String msg) {
57 | return Log.w(tag, msg);
58 | }
59 |
60 | @Override
61 | public int w(String tag, String msg, Throwable tr) {
62 | return Log.w(tag, msg, tr);
63 | }
64 |
65 | @Override
66 | public int e(String tag, String msg) {
67 | return Log.e(tag, msg);
68 | }
69 |
70 | @Override
71 | public int e(String tag, String msg, Throwable tr) {
72 | return Log.e(tag, msg, tr);
73 | }
74 |
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/src/com/others/scrollerproxy/GingerScroller.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright 2011, 2012 Chris Banes.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *******************************************************************************/
16 | package com.others.scrollerproxy;
17 |
18 | import android.annotation.TargetApi;
19 | import android.content.Context;
20 | import android.widget.OverScroller;
21 |
22 | @TargetApi(9)
23 | public class GingerScroller extends ScrollerProxy {
24 |
25 | protected final OverScroller mScroller;
26 | private boolean mFirstScroll = false;
27 |
28 | public GingerScroller(Context context) {
29 | mScroller = new OverScroller(context);
30 | }
31 |
32 | @Override
33 | public boolean computeScrollOffset() {
34 | // Workaround for first scroll returning 0 for the direction of the edge it hits.
35 | // Simply recompute values.
36 | if (mFirstScroll) {
37 | mScroller.computeScrollOffset();
38 | mFirstScroll = false;
39 | }
40 | return mScroller.computeScrollOffset();
41 | }
42 |
43 | @Override
44 | public void fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY,
45 | int overX, int overY) {
46 | mScroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY, overX, overY);
47 | }
48 |
49 | @Override
50 | public void forceFinished(boolean finished) {
51 | mScroller.forceFinished(finished);
52 | }
53 |
54 | @Override
55 | public boolean isFinished() {
56 | return mScroller.isFinished();
57 | }
58 |
59 | @Override
60 | public int getCurrX() {
61 | return mScroller.getCurrX();
62 | }
63 |
64 | @Override
65 | public int getCurrY() {
66 | return mScroller.getCurrY();
67 | }
68 | }
--------------------------------------------------------------------------------
/src/com/others/scrollerproxy/IcsScroller.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright 2011, 2012 Chris Banes.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *******************************************************************************/
16 | package com.others.scrollerproxy;
17 |
18 | import android.annotation.TargetApi;
19 | import android.content.Context;
20 |
21 | @TargetApi(14)
22 | public class IcsScroller extends GingerScroller {
23 |
24 | public IcsScroller(Context context) {
25 | super(context);
26 | }
27 |
28 | @Override
29 | public boolean computeScrollOffset() {
30 | return mScroller.computeScrollOffset();
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/src/com/others/scrollerproxy/PreGingerScroller.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright 2011, 2012 Chris Banes.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *******************************************************************************/
16 | package com.others.scrollerproxy;
17 |
18 | import android.content.Context;
19 | import android.widget.Scroller;
20 |
21 | public class PreGingerScroller extends ScrollerProxy {
22 |
23 | private final Scroller mScroller;
24 |
25 | public PreGingerScroller(Context context) {
26 | mScroller = new Scroller(context);
27 | }
28 |
29 | @Override
30 | public boolean computeScrollOffset() {
31 | return mScroller.computeScrollOffset();
32 | }
33 |
34 | @Override
35 | public void fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY,
36 | int overX, int overY) {
37 | mScroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY);
38 | }
39 |
40 | @Override
41 | public void forceFinished(boolean finished) {
42 | mScroller.forceFinished(finished);
43 | }
44 |
45 | public boolean isFinished() {
46 | return mScroller.isFinished();
47 | }
48 |
49 | @Override
50 | public int getCurrX() {
51 | return mScroller.getCurrX();
52 | }
53 |
54 | @Override
55 | public int getCurrY() {
56 | return mScroller.getCurrY();
57 | }
58 | }
--------------------------------------------------------------------------------
/src/com/others/scrollerproxy/ScrollerProxy.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright 2011, 2012 Chris Banes.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *******************************************************************************/
16 | package com.others.scrollerproxy;
17 |
18 | import android.content.Context;
19 | import android.os.Build.VERSION;
20 | import android.os.Build.VERSION_CODES;
21 |
22 | public abstract class ScrollerProxy {
23 |
24 | public static ScrollerProxy getScroller(Context context) {
25 | if (VERSION.SDK_INT < VERSION_CODES.GINGERBREAD) {
26 | return new PreGingerScroller(context);
27 | } else if (VERSION.SDK_INT < VERSION_CODES.ICE_CREAM_SANDWICH) {
28 | return new GingerScroller(context);
29 | } else {
30 | return new IcsScroller(context);
31 | }
32 | }
33 |
34 | public abstract boolean computeScrollOffset();
35 |
36 | public abstract void fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY,
37 | int maxY, int overX, int overY);
38 |
39 | public abstract void forceFinished(boolean finished);
40 |
41 | public abstract boolean isFinished();
42 |
43 | public abstract int getCurrX();
44 |
45 | public abstract int getCurrY();
46 |
47 |
48 | }
49 |
--------------------------------------------------------------------------------