├── .idea ├── .name ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── libraries │ ├── android_support_v4.xml │ └── universal_image_loader_1_9_2_with_sources.xml ├── misc.xml ├── modules.xml └── vcs.xml ├── AndroidManifest.xml ├── README.md ├── WeiXinLookImgsDemo.iml ├── ant.properties ├── build.xml ├── gen └── com │ └── hankkin │ └── WeiXinLookImgsDemo │ ├── BuildConfig.java │ ├── Manifest.java │ └── R.java ├── libs └── universal-image-loader-1.9.2-with-sources.jar ├── local.properties ├── proguard-project.txt ├── project.properties ├── res ├── drawable-hdpi │ └── ic_launcher.png ├── drawable-ldpi │ └── ic_launcher.png ├── drawable-mdpi │ └── ic_launcher.png ├── drawable-xhdpi │ └── ic_launcher.png ├── layout │ ├── gridview_item.xml │ ├── image_detail_fragment.xml │ ├── image_detail_pager.xml │ ├── item.xml │ └── main.xml └── values │ └── strings.xml ├── screenshot ├── QQ20151123-0@2x.png ├── QQ20151123-1@2x.png ├── QQ20151123-2@2x.png ├── QQ20151123-3@2x.png └── screenshot.gif └── src └── com ├── hankkin └── WeiXinLookImgsDemo │ ├── MyActivity.java │ ├── activty │ ├── ImageDetailFragment.java │ └── ImagePagerActivity.java │ ├── adapter │ ├── ImageGridAdapter.java │ └── MyAdapter.java │ ├── application │ └── MyApplication.java │ ├── model │ └── ContentBean.java │ └── view │ ├── HackyViewPager.java │ ├── IPhotoView.java │ └── NoScrollGridView.java └── others ├── Compat.java ├── DefaultOnDoubleTapListener.java ├── IPhotoView.java ├── PhotoView.java ├── PhotoViewAttacher.java ├── gestures ├── CupcakeGestureDetector.java ├── EclairGestureDetector.java ├── FroyoGestureDetector.java ├── GestureDetector.java ├── OnGestureListener.java └── VersionedGestureDetector.java ├── log ├── LogManager.java ├── Logger.java └── LoggerDefault.java └── scrollerproxy ├── GingerScroller.java ├── IcsScroller.java ├── PreGingerScroller.java └── ScrollerProxy.java /.idea/.name: -------------------------------------------------------------------------------- 1 | WeiXinLookImgsDemo -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/libraries/android_support_v4.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/libraries/universal_image_loader_1_9_2_with_sources.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 50 | 51 | 52 | 64 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 91 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WeiXinLookImgsDemo 2 | 高仿微信朋友发表图片说说 3 | 4 | 高仿微信群聊头像 5 | 6 | 高仿微信群聊头像 7 | 8 | 高仿微信群聊头像 9 | 10 | 高仿微信群聊头像 11 | 12 | 高仿微信群聊头像 13 | 14 | -------------------------------------------------------------------------------- /WeiXinLookImgsDemo.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 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 | --------------------------------------------------------------------------------