├── .gitignore ├── .idea ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── jchou │ │ └── imagepagers │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── jchou │ │ │ └── imagepagers │ │ │ ├── MainActivity.java │ │ │ └── MainAdapter.java │ └── res │ │ ├── layout │ │ ├── activity_main.xml │ │ └── item_activity_main.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── jchou │ └── imagepagers │ └── ExampleUnitTest.java ├── build.gradle ├── gif └── imagereview.gif ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── imagereview ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── jchou │ │ └── imagereview │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── jchou │ │ │ └── imagereview │ │ │ ├── adapter │ │ │ └── ImagePagerAdapter.java │ │ │ ├── glide │ │ │ ├── OkHttpProgressGlideModule.java │ │ │ ├── ProgressTarget.java │ │ │ └── WrappingTarget.java │ │ │ ├── ui │ │ │ ├── ImageDetailFragment.java │ │ │ └── ImagePagerActivity.java │ │ │ ├── util │ │ │ └── ScreenUtils.java │ │ │ └── widget │ │ │ ├── AdjustImageView.java │ │ │ └── DragViewPager.java │ └── res │ │ ├── layout │ │ ├── image_detail_fragment.xml │ │ └── image_detail_page.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ ├── refresh_loading01.png │ │ ├── refresh_loading02.png │ │ ├── refresh_loading03.png │ │ ├── refresh_loading04.png │ │ ├── refresh_loading05.png │ │ ├── refresh_loading06.png │ │ ├── refresh_loading07.png │ │ ├── refresh_loading08.png │ │ ├── refresh_loading09.png │ │ ├── refresh_loading10.png │ │ ├── refresh_loading11.png │ │ └── refresh_loading12.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── color.xml │ │ ├── ids.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── jchou │ └── imagereview │ └── ExampleUnitTest.java └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ImageReview 2 | 仿微信图片详情页面,可下拉关闭页面 3 | 4 | 效果如下 5 | 6 | ![imagereview.gif](https://github.com/JohnsonHou/ImageReview/blob/HEAD/gif/imagereview.gif) 7 | 8 | 如何使用 9 | 10 | ```` 11 | compile 'com.jchou.android.imagereview:imagereview:1.0.1' 12 | ```` 13 | 14 | 首先在需要使用的页面 15 | 16 | ```` 17 | //MainActivity调用的页面,urls传入的图片数据,pos传入的图片位置,view需要共享的view 18 | ImagePagerActivity.startImagePage(MainActivity.this, 19 |                        urls,pos,view); 20 | ```` 21 | 22 | 其次设置转场动画的共享元素,因为跳转和返回时都会调用onMapSharedElements,需要判断bundle是否为空,bundle会在返回的时候在onActivityReenter获取 23 | ```` 24 | //设置转场动画的共享元素,因为跳转和返回时都会调用onMapSharedElements,需要判断bundle是否为空 25 | setExitSharedElementCallback(new SharedElementCallback() { 26 | @Override 27 | public void onMapSharedElements(List names, Map sharedElements) { 28 | if (bundle!=null){ 29 | int index = bundle.getInt(ImagePagerActivity.STATE_POSITION,0); 30 | sharedElements.clear(); 31 | sharedElements.put("img", recyclerView.getLayoutManager().findViewByPosition(index)); 32 | bundle=null; 33 | } 34 | } 35 | }); 36 | ```` 37 | 38 | 在返回的时候获取数据bundle 39 | ```` 40 | @Override 41 | public void onActivityReenter(int resultCode, Intent data) { 42 | super.onActivityReenter(resultCode, data); 43 | bundle = data.getExtras(); 44 | int currentPosition = bundle.getInt(ImagePagerActivity.STATE_POSITION,0); 45 | //做相应的滚动 46 | recyclerView.scrollToPosition(currentPosition); 47 | //暂时延迟 Transition 的使用,直到我们确定了共享元素的确切大小和位置才使用 48 | //postponeEnterTransition后不要忘记调用startPostponedEnterTransition 49 | ActivityCompat.postponeEnterTransition(this); 50 | recyclerView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { 51 | @Override 52 | public boolean onPreDraw() { 53 | recyclerView.getViewTreeObserver().removeOnPreDrawListener(this); 54 | // TODO: figure out why it is necessary to request layout here in order to get a smooth transition. 55 | recyclerView.requestLayout(); 56 | //共享元素准备好后调用startPostponedEnterTransition来恢复过渡效果 57 | ActivityCompat.startPostponedEnterTransition(MainActivity.this); 58 | return true; 59 | } 60 | }); 61 | } 62 | ```` 63 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "25.0.2" 6 | defaultConfig { 7 | applicationId "com.jchou.imagepagers" 8 | minSdkVersion 16 9 | targetSdkVersion 25 10 | versionCode 1 11 | versionName '1.0.0' 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(include: ['*.jar'], dir: 'libs') 24 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 25 | exclude group: 'com.android.support', module: 'support-annotations' 26 | }) 27 | compile 'com.android.support:appcompat-v7:25.+' 28 | testCompile 'junit:junit:4.12' 29 | compile 'com.android.support:recyclerview-v7:25.3.1' 30 | compile 'com.github.bumptech.glide:glide:3.7.0' 31 | // compile project(':imagereview') 32 | compile 'com.jchou.android.imagereview:imagereview:1.0.1' 33 | } 34 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/Johnson/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/jchou/imagepagers/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.jchou.imagepagers; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.jchou.imagepagers", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/jchou/imagepagers/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.jchou.imagepagers; 2 | 3 | import android.content.Intent; 4 | import android.support.v4.app.ActivityCompat; 5 | import android.support.v4.app.SharedElementCallback; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.os.Bundle; 8 | import android.support.v7.widget.GridLayoutManager; 9 | import android.support.v7.widget.RecyclerView; 10 | import android.util.Log; 11 | import android.view.View; 12 | import android.view.ViewTreeObserver; 13 | 14 | import com.jchou.imagereview.ui.ImagePagerActivity; 15 | 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | import java.util.Map; 19 | 20 | public class MainActivity extends AppCompatActivity { 21 | 22 | private RecyclerView recyclerView; 23 | 24 | private MainAdapter mainAdapter; 25 | 26 | //图片集合 27 | private ArrayList urls; 28 | 29 | //存放返回时当前页码 30 | private Bundle bundle; 31 | 32 | @Override 33 | protected void onCreate(Bundle savedInstanceState) { 34 | super.onCreate(savedInstanceState); 35 | setContentView(R.layout.activity_main); 36 | urls=new ArrayList<>(); 37 | //为了显示效果,重复添加了三次 38 | urls.add("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1524477979306&di=3eb07e9302606048abe13d7b6a2bc601&imgtype=0&src=http%3A%2F%2Fimg4.duitang.com%2Fuploads%2Fitem%2F201406%2F12%2F20140612211118_YYXAC.jpeg"); 39 | urls.add("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1524133463580&di=1315bc4db30999f00b89ef79c3bb06e5&imgtype=0&src=http%3A%2F%2Fpic36.photophoto.cn%2F20150710%2F0005018721870517_b.jpg"); 40 | urls.add("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1524133463575&di=6221f21bcb761675c5d161ebc53d5948&imgtype=0&src=http%3A%2F%2Fimg5.duitang.com%2Fuploads%2Fitem%2F201410%2F03%2F20141003112442_AkkuH.thumb.700_0.jpeg"); 41 | 42 | urls.add("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1524477979306&di=3eb07e9302606048abe13d7b6a2bc601&imgtype=0&src=http%3A%2F%2Fimg4.duitang.com%2Fuploads%2Fitem%2F201406%2F12%2F20140612211118_YYXAC.jpeg"); 43 | urls.add("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1524133463580&di=1315bc4db30999f00b89ef79c3bb06e5&imgtype=0&src=http%3A%2F%2Fpic36.photophoto.cn%2F20150710%2F0005018721870517_b.jpg"); 44 | urls.add("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1524133463575&di=6221f21bcb761675c5d161ebc53d5948&imgtype=0&src=http%3A%2F%2Fimg5.duitang.com%2Fuploads%2Fitem%2F201410%2F03%2F20141003112442_AkkuH.thumb.700_0.jpeg"); 45 | 46 | urls.add("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1524477979306&di=3eb07e9302606048abe13d7b6a2bc601&imgtype=0&src=http%3A%2F%2Fimg4.duitang.com%2Fuploads%2Fitem%2F201406%2F12%2F20140612211118_YYXAC.jpeg"); 47 | urls.add("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1524133463580&di=1315bc4db30999f00b89ef79c3bb06e5&imgtype=0&src=http%3A%2F%2Fpic36.photophoto.cn%2F20150710%2F0005018721870517_b.jpg"); 48 | urls.add("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1524133463575&di=6221f21bcb761675c5d161ebc53d5948&imgtype=0&src=http%3A%2F%2Fimg5.duitang.com%2Fuploads%2Fitem%2F201410%2F03%2F20141003112442_AkkuH.thumb.700_0.jpeg"); 49 | initView(); 50 | } 51 | 52 | private void initView() { 53 | recyclerView = (RecyclerView) findViewById(R.id.recycler); 54 | recyclerView.setLayoutManager(new GridLayoutManager(this, 2)); 55 | mainAdapter = new MainAdapter(urls); 56 | mainAdapter.setOnItemClickListener(new MainAdapter.OnItemClickListener() { 57 | @Override 58 | public void onItemClick(int pos) { 59 | ImagePagerActivity.startImagePage(MainActivity.this, 60 | urls,pos,recyclerView.getLayoutManager().findViewByPosition(pos)); 61 | } 62 | }); 63 | recyclerView.setAdapter(mainAdapter); 64 | 65 | //设置转场动画的共享元素,因为跳转和返回都会调用,需要判断bundle是否为空 66 | setExitSharedElementCallback(new SharedElementCallback() { 67 | @Override 68 | public void onMapSharedElements(List names, Map sharedElements) { 69 | if (bundle!=null){ 70 | int index = bundle.getInt(ImagePagerActivity.STATE_POSITION,0); 71 | sharedElements.clear(); 72 | sharedElements.put("img", recyclerView.getLayoutManager().findViewByPosition(index)); 73 | bundle=null; 74 | } 75 | } 76 | }); 77 | } 78 | 79 | 80 | //返回的时候获取数据 81 | @Override 82 | public void onActivityReenter(int resultCode, Intent data) { 83 | super.onActivityReenter(resultCode, data); 84 | bundle = data.getExtras(); 85 | int currentPosition = bundle.getInt(ImagePagerActivity.STATE_POSITION,0); 86 | //做相应的滚动 87 | recyclerView.scrollToPosition(currentPosition); 88 | //暂时延迟 Transition 的使用,直到我们确定了共享元素的确切大小和位置才使用 89 | //postponeEnterTransition后不要忘记调用startPostponedEnterTransition 90 | ActivityCompat.postponeEnterTransition(this); 91 | recyclerView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { 92 | @Override 93 | public boolean onPreDraw() { 94 | recyclerView.getViewTreeObserver().removeOnPreDrawListener(this); 95 | // TODO: figure out why it is necessary to request layout here in order to get a smooth transition. 96 | recyclerView.requestLayout(); 97 | //共享元素准备好后调用startPostponedEnterTransition来恢复过渡效果 98 | ActivityCompat.startPostponedEnterTransition(MainActivity.this); 99 | return true; 100 | } 101 | }); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /app/src/main/java/com/jchou/imagepagers/MainAdapter.java: -------------------------------------------------------------------------------- 1 | package com.jchou.imagepagers; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.ImageView; 9 | 10 | import com.bumptech.glide.Glide; 11 | 12 | import java.util.List; 13 | 14 | /** 15 | * Created by Johnson on 2018/3/27. 16 | */ 17 | 18 | public class MainAdapter extends RecyclerView.Adapter { 19 | 20 | public void setOnItemClickListener(OnItemClickListener onItemClickListener) { 21 | this.onItemClickListener = onItemClickListener; 22 | } 23 | 24 | private OnItemClickListener onItemClickListener; 25 | 26 | public interface OnItemClickListener { 27 | void onItemClick(int pos); 28 | } 29 | 30 | private List mDatas; 31 | private Context mContext; 32 | 33 | public MainAdapter(List datas) { 34 | mDatas = datas; 35 | } 36 | 37 | @Override 38 | public MainAdapter.Holder onCreateViewHolder(ViewGroup parent, int viewType) { 39 | mContext=parent.getContext(); 40 | return new Holder(LayoutInflater.from(mContext).inflate(R.layout.item_activity_main, parent, false)); 41 | } 42 | 43 | @Override 44 | public void onBindViewHolder(MainAdapter.Holder holder, final int position) { 45 | holder.itemView.setOnClickListener(new View.OnClickListener() { 46 | @Override 47 | public void onClick(View v) { 48 | if (onItemClickListener != null) { 49 | onItemClickListener.onItemClick(position); 50 | } 51 | } 52 | }); 53 | Glide.with(mContext).load(mDatas.get(position)) 54 | .placeholder(R.mipmap.ic_launcher).dontAnimate() 55 | .into(holder.imageView); 56 | } 57 | 58 | 59 | @Override 60 | public int getItemCount() { 61 | return mDatas == null ? 0 : mDatas.size(); 62 | } 63 | 64 | class Holder extends RecyclerView.ViewHolder { 65 | 66 | private ImageView imageView; 67 | 68 | public Holder(View itemView) { 69 | super(itemView); 70 | imageView = (ImageView) itemView.findViewById(R.id.iv_item_main_activity_image); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnsonHou/ImageReview/7b882eb0a2463d128e6c667428a3810e67d37424/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnsonHou/ImageReview/7b882eb0a2463d128e6c667428a3810e67d37424/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnsonHou/ImageReview/7b882eb0a2463d128e6c667428a3810e67d37424/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnsonHou/ImageReview/7b882eb0a2463d128e6c667428a3810e67d37424/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnsonHou/ImageReview/7b882eb0a2463d128e6c667428a3810e67d37424/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnsonHou/ImageReview/7b882eb0a2463d128e6c667428a3810e67d37424/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnsonHou/ImageReview/7b882eb0a2463d128e6c667428a3810e67d37424/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnsonHou/ImageReview/7b882eb0a2463d128e6c667428a3810e67d37424/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnsonHou/ImageReview/7b882eb0a2463d128e6c667428a3810e67d37424/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnsonHou/ImageReview/7b882eb0a2463d128e6c667428a3810e67d37424/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | ImagePagers 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/test/java/com/jchou/imagepagers/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.jchou.imagepagers; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.3.3' 9 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.2' 10 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' 11 | // NOTE: Do not place your application dependencies here; they belong 12 | // in the individual module build.gradle files 13 | } 14 | } 15 | 16 | allprojects { 17 | repositories { 18 | jcenter() 19 | } 20 | } 21 | 22 | task clean(type: Delete) { 23 | delete rootProject.buildDir 24 | } 25 | -------------------------------------------------------------------------------- /gif/imagereview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnsonHou/ImageReview/7b882eb0a2463d128e6c667428a3810e67d37424/gif/imagereview.gif -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | 19 | # 以下信息替换为自己的 20 | PROJ_GROUP=com.jchou.android.imagereview 21 | PROJ_VERSION=1.0.1 22 | PROJ_NAME=AndroidImageReview 23 | PROJ_WEBSITEURL=https://github.com/JohnsonHou/ImageReview 24 | PROJ_ISSUETRACKERURL=https://github.com/JohnsonHou/ImageReview/issues 25 | PROJ_VCSURL=https://github.com/JohnsonHou/ImageReview.git 26 | PROJ_DESCRIPTION=Android ImageReview Library 27 | PROJ_ARTIFACTID=AndroidImageReviewLib 28 | 29 | LICENSE_NAME='The Apache Software License, Version 2.0' 30 | LICENSE_URL='http://www.apache.org/licenses/LICENSE-2.0.txt' 31 | 32 | DEVELOPER_ID=johnsonhou 33 | DEVELOPER_NAME=johnsonhou 34 | DEVELOPER_EMAIL=jchomme7@gmail.com 35 | 36 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnsonHou/ImageReview/7b882eb0a2463d128e6c667428a3810e67d37424/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Apr 28 14:29:45 CST 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /imagereview/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /imagereview/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion '25.0.2' 6 | 7 | defaultConfig { 8 | minSdkVersion 16 9 | targetSdkVersion 25 10 | versionCode 1 11 | versionName '1.0.0' 12 | 13 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 14 | 15 | } 16 | buildTypes { 17 | release { 18 | minifyEnabled false 19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 20 | } 21 | } 22 | } 23 | 24 | dependencies { 25 | compile fileTree(include: ['*.jar'], dir: 'libs') 26 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 27 | exclude group: 'com.android.support', module: 'support-annotations' 28 | }) 29 | compile 'com.android.support:appcompat-v7:25.+' 30 | testCompile 'junit:junit:4.12' 31 | compile 'com.github.bumptech.glide:glide:3.7.0' 32 | compile 'com.github.bumptech.glide:okhttp3-integration:1.4.0@aar' 33 | compile 'com.davemorrissey.labs:subsampling-scale-image-view:3.5.0' 34 | compile 'com.squareup.okhttp3:okhttp:3.8.1' 35 | compile 'com.nineoldandroids:library:2.4.0' 36 | } 37 | 38 | apply plugin: 'com.github.dcendents.android-maven' 39 | apply plugin: 'com.jfrog.bintray' 40 | 41 | //Properties prop = new Properties() 42 | //prop.load(project.rootProject.file('gradle.properties').newDataInputStream()) 43 | 44 | def siteUrl = PROJ_WEBSITEURL // 项目主页 45 | def gitUrl = PROJ_VCSURL // //项目的版本控制地址 46 | group = PROJ_GROUP // //发布到组织名称名字,必须填写 47 | 48 | //发布到JCenter上的项目名字,必须填写 49 | def libName = "AndroidImageReviewLib" 50 | 51 | // 版本号 52 | version = PROJ_VERSION 53 | /** 上面配置后上传至jcenter后的编译路径是这样的: compile 'com.jchou.android.imagereview/ImageReview:1.0.1' **/ 54 | 55 | 56 | //生成源文件 57 | task sourcesJar(type: Jar) { 58 | from android.sourceSets.main.java.srcDirs 59 | classifier = 'sources' 60 | } 61 | 62 | //生成文档 63 | task javadoc(type: Javadoc) { 64 | source = android.sourceSets.main.java.srcDirs 65 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 66 | options.encoding "UTF-8" 67 | options.charSet 'UTF-8' 68 | options.author true 69 | options.version true 70 | // options.links "https://github.com/linglongxin24/FastDev/tree/master/mylibrary/docs/javadoc" 71 | failOnError false 72 | } 73 | 74 | //文档打包成jar 75 | task javadocJar(type: Jar, dependsOn: javadoc) { 76 | classifier = 'javadoc' 77 | from javadoc.destinationDir 78 | } 79 | 80 | //拷贝javadoc文件 81 | task copyDoc(type: Copy) { 82 | from "${buildDir}/docs/" 83 | into "docs" 84 | } 85 | 86 | javadoc { 87 | options { 88 | encoding "UTF-8" 89 | } 90 | } 91 | //上传到jcenter所需要的源码文件 92 | artifacts { 93 | archives javadocJar 94 | archives sourcesJar 95 | } 96 | 97 | // 配置maven库,生成POM.xml文件 98 | install { 99 | repositories.mavenInstaller { 100 | // This generates POM.xml with proper parameters 101 | pom { 102 | project { 103 | packaging 'aar' // Add your description here 104 | name PROJ_ARTIFACTID 105 | description PROJ_DESCRIPTION 106 | url siteUrl // Set your license 107 | licenses { 108 | license { 109 | name LICENSE_NAME 110 | url LICENSE_URL 111 | } 112 | } 113 | developers { 114 | developer { 115 | id DEVELOPER_ID 116 | name DEVELOPER_NAME 117 | email DEVELOPER_EMAIL 118 | } 119 | } 120 | scm { 121 | connection gitUrl 122 | developerConnection gitUrl 123 | url siteUrl 124 | } 125 | } 126 | } 127 | } 128 | } 129 | 130 | //上传到jcenter 131 | Properties properties = new Properties() 132 | properties.load(project.rootProject.file('local.properties').newDataInputStream()) 133 | 134 | 135 | bintray { 136 | user = properties.getProperty("bintray.user")//读取 local.properties 文件里面的 bintray.user 137 | key = properties.getProperty("bintray.apikey")//读取 local.properties 文件里面的 bintray.apikey 138 | 139 | configurations = ['archives'] 140 | pkg { 141 | repo = "maven" // it is the name that appears in bintray when logged 142 | name = libName //发布到JCenter上的项目名字,必须填写 143 | desc = 'This is a skin change framework' //项目描述 144 | websiteUrl = siteUrl 145 | vcsUrl = gitUrl 146 | licenses = ["Apache-2.0"] 147 | publish = true 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /imagereview/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/Johnson/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /imagereview/src/androidTest/java/com/jchou/imagereview/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.jchou.imagereview; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.jchou.imagereview.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /imagereview/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 7 | 8 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /imagereview/src/main/java/com/jchou/imagereview/adapter/ImagePagerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.jchou.imagereview.adapter; 2 | 3 | import android.support.v4.app.Fragment; 4 | import android.support.v4.app.FragmentManager; 5 | import android.support.v4.app.FragmentStatePagerAdapter; 6 | import android.util.Log; 7 | import android.view.View; 8 | 9 | import com.jchou.imagereview.ui.ImageDetailFragment; 10 | import com.jchou.imagereview.widget.DragViewPager; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | 16 | public class ImagePagerAdapter extends FragmentStatePagerAdapter { 17 | private DragViewPager mPager; 18 | private ArrayList mFragmentList; 19 | 20 | public ImagePagerAdapter(FragmentManager fm, List datas,DragViewPager pager) { 21 | super(fm); 22 | mPager=pager; 23 | mPager.setAdapter(this); 24 | updateData(datas); 25 | } 26 | 27 | public void updateData(List dataList) { 28 | ArrayList fragments = new ArrayList<>(); 29 | for (int i = 0, size = dataList.size(); i < size; i++) { 30 | final ImageDetailFragment fragment = ImageDetailFragment.newInstance(dataList.get(i)); 31 | fragment.setOnImageListener(new ImageDetailFragment.OnImageListener() { 32 | @Override 33 | public void onInit() { 34 | View view = fragment.getView(); 35 | mPager.setCurrentShowView(view); 36 | } 37 | }); 38 | fragments.add(fragment); 39 | } 40 | setViewList(fragments); 41 | } 42 | 43 | private void setViewList(ArrayList fragmentList) { 44 | if (mFragmentList != null) { 45 | mFragmentList.clear(); 46 | } 47 | mFragmentList = fragmentList; 48 | notifyDataSetChanged(); 49 | } 50 | 51 | @Override 52 | public int getCount() { 53 | return mFragmentList==null?0:mFragmentList.size(); 54 | } 55 | 56 | public int getItemPosition(Object object) { 57 | return POSITION_NONE; 58 | } 59 | 60 | @Override 61 | public Fragment getItem(int position) { 62 | return mFragmentList.get(position); 63 | } 64 | 65 | 66 | } -------------------------------------------------------------------------------- /imagereview/src/main/java/com/jchou/imagereview/glide/OkHttpProgressGlideModule.java: -------------------------------------------------------------------------------- 1 | package com.jchou.imagereview.glide; 2 | 3 | import android.content.Context; 4 | import android.os.Handler; 5 | import android.os.Looper; 6 | 7 | import com.bumptech.glide.Glide; 8 | import com.bumptech.glide.GlideBuilder; 9 | import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader; 10 | import com.bumptech.glide.load.model.GlideUrl; 11 | import com.bumptech.glide.module.GlideModule; 12 | 13 | import java.io.IOException; 14 | import java.io.InputStream; 15 | import java.util.Map; 16 | import java.util.concurrent.ConcurrentHashMap; 17 | 18 | import okhttp3.HttpUrl; 19 | import okhttp3.Interceptor; 20 | import okhttp3.MediaType; 21 | import okhttp3.OkHttpClient; 22 | import okhttp3.Request; 23 | import okhttp3.Response; 24 | import okhttp3.ResponseBody; 25 | import okio.Buffer; 26 | import okio.BufferedSource; 27 | import okio.ForwardingSource; 28 | import okio.Okio; 29 | import okio.Source; 30 | 31 | // TODO add 32 | // TODO add 33 | // or not use 'okhttp@aar' in Gradle depdendencies 34 | public class OkHttpProgressGlideModule implements GlideModule { 35 | @Override 36 | public void applyOptions(Context context, GlideBuilder builder) { 37 | } 38 | 39 | @Override 40 | public void registerComponents(Context context, Glide glide) { 41 | OkHttpClient.Builder builder = new OkHttpClient.Builder(); 42 | builder.networkInterceptors().add(createInterceptor(new DispatchingProgressListener())); 43 | glide.register(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory(builder.build())); 44 | } 45 | 46 | private static Interceptor createInterceptor(final ResponseProgressListener listener) { 47 | return new Interceptor() { 48 | @Override 49 | public Response intercept(Chain chain) throws IOException { 50 | Request request = chain.request(); 51 | Response response = chain.proceed(request); 52 | return response.newBuilder() 53 | .body(new OkHttpProgressResponseBody(request.url(), response.body(), listener)) 54 | .build(); 55 | } 56 | }; 57 | } 58 | 59 | public interface UIProgressListener { 60 | void onProgress(long bytesRead, long expectedLength); 61 | 62 | /** 63 | * Control how often the listener needs an update. 0% and 100% will always be dispatched. 64 | * 65 | * @return in percentage (0.2 = call {@link #onProgress} around every 0.2 percent of progress) 66 | */ 67 | float getGranualityPercentage(); 68 | } 69 | 70 | public static void forget(String url) { 71 | DispatchingProgressListener.forget(url); 72 | } 73 | 74 | public static void expect(String url, UIProgressListener listener) { 75 | DispatchingProgressListener.expect(url, listener); 76 | } 77 | 78 | private interface ResponseProgressListener { 79 | void update(HttpUrl url, long bytesRead, long contentLength); 80 | } 81 | 82 | private static class DispatchingProgressListener implements ResponseProgressListener { 83 | private static final Map LISTENERS = new ConcurrentHashMap<>(); 84 | private static final Map PROGRESSES = new ConcurrentHashMap<>(); 85 | 86 | private final Handler handler; 87 | 88 | DispatchingProgressListener() { 89 | this.handler = new Handler(Looper.getMainLooper()); 90 | } 91 | 92 | static void forget(String url) { 93 | LISTENERS.remove(url); 94 | PROGRESSES.remove(url); 95 | } 96 | 97 | static void expect(String url, UIProgressListener listener) { 98 | LISTENERS.put(url, listener); 99 | } 100 | 101 | @Override 102 | public void update(HttpUrl url, final long bytesRead, final long contentLength) { 103 | //System.out.printf("%s: %d/%d = %.2f%%%n", url, bytesRead, contentLength, (100f * bytesRead) / contentLength); 104 | String key = url.toString(); 105 | final UIProgressListener listener = LISTENERS.get(key); 106 | if (listener == null) { 107 | return; 108 | } 109 | //长度是错误的移除监听 110 | if (contentLength <= bytesRead) { 111 | forget(key); 112 | } 113 | if (needsDispatch(key, bytesRead, contentLength, listener.getGranualityPercentage())) { 114 | handler.post(new Runnable() { 115 | @Override 116 | public void run() { 117 | listener.onProgress(bytesRead, contentLength); 118 | } 119 | }); 120 | } 121 | } 122 | 123 | private boolean needsDispatch(String key, long current, long total, float granularity) { 124 | if (granularity == 0 || current == 0 || total == current) { 125 | return true; 126 | } 127 | float percent = 100f * current / total; 128 | long currentProgress = (long) (percent / granularity); 129 | Long lastProgress = PROGRESSES.get(key); 130 | if (lastProgress == null || currentProgress != lastProgress) { 131 | PROGRESSES.put(key, currentProgress); 132 | return true; 133 | } else { 134 | return false; 135 | } 136 | } 137 | } 138 | 139 | private static class OkHttpProgressResponseBody extends ResponseBody { 140 | private final HttpUrl url; 141 | private final ResponseBody responseBody; 142 | private final ResponseProgressListener progressListener; 143 | private BufferedSource bufferedSource; 144 | 145 | OkHttpProgressResponseBody(HttpUrl url, ResponseBody responseBody, 146 | ResponseProgressListener progressListener) { 147 | this.url = url; 148 | this.responseBody = responseBody; 149 | this.progressListener = progressListener; 150 | } 151 | 152 | @Override 153 | public MediaType contentType() { 154 | return responseBody.contentType(); 155 | } 156 | 157 | @Override 158 | public long contentLength() { 159 | return responseBody.contentLength(); 160 | } 161 | 162 | @Override 163 | public BufferedSource source() { 164 | if (bufferedSource == null) { 165 | bufferedSource = Okio.buffer(source(responseBody.source())); 166 | } 167 | return bufferedSource; 168 | } 169 | 170 | private Source source(Source source) { 171 | return new ForwardingSource(source) { 172 | long totalBytesRead = 0L; 173 | 174 | @Override 175 | public long read(Buffer sink, long byteCount) throws IOException { 176 | long bytesRead = super.read(sink, byteCount); 177 | long fullLength = responseBody.contentLength(); 178 | if (bytesRead == -1) { // this source is exhausted 179 | totalBytesRead = fullLength; 180 | } else { 181 | totalBytesRead += bytesRead; 182 | } 183 | progressListener.update(url, totalBytesRead, fullLength); 184 | return bytesRead; 185 | } 186 | }; 187 | } 188 | } 189 | } -------------------------------------------------------------------------------- /imagereview/src/main/java/com/jchou/imagereview/glide/ProgressTarget.java: -------------------------------------------------------------------------------- 1 | package com.jchou.imagereview.glide; 2 | 3 | import android.graphics.drawable.Drawable; 4 | 5 | import com.bumptech.glide.Glide; 6 | import com.bumptech.glide.request.animation.GlideAnimation; 7 | import com.bumptech.glide.request.target.Target; 8 | 9 | public abstract class ProgressTarget extends WrappingTarget implements OkHttpProgressGlideModule.UIProgressListener { 10 | private T model; 11 | private boolean ignoreProgress = true; 12 | 13 | public ProgressTarget(T model, Target target) { 14 | super(target); 15 | this.model = model; 16 | } 17 | 18 | public final T getModel() { 19 | return model; 20 | } 21 | 22 | public final void setModel(T model) { 23 | Glide.clear(this); // indirectly calls cleanup 24 | this.model = model; 25 | } 26 | 27 | /** 28 | * Convert a model into an Url string that is used to match up the OkHttp requests. For explicit 29 | * {@link com.bumptech.glide.load.model.GlideUrl GlideUrl} loads this needs to return 30 | * {@link com.bumptech.glide.load.model.GlideUrl#toStringUrl toStringUrl}. For custom models do the same as your 31 | * {@link com.bumptech.glide.load.model.stream.BaseGlideUrlLoader BaseGlideUrlLoader} does. 32 | * 33 | * @param model return the representation of the given model, DO NOT use {@link #getModel()} inside this method. 34 | * @return a stable Url representation of the model, otherwise the progress reporting won't work 35 | */ 36 | protected String toUrlString(T model) { 37 | return String.valueOf(model); 38 | } 39 | 40 | @Override 41 | public float getGranualityPercentage() { 42 | return 1.0f; 43 | } 44 | 45 | private void start() { 46 | OkHttpProgressGlideModule.expect(toUrlString(model), this); 47 | ignoreProgress = false; 48 | } 49 | 50 | private void cleanup() { 51 | ignoreProgress = true; 52 | T model = this.model; // save in case it gets modified 53 | OkHttpProgressGlideModule.forget(toUrlString(model)); 54 | this.model = null; 55 | } 56 | 57 | @Override 58 | public void onLoadStarted(Drawable placeholder) { 59 | super.onLoadStarted(placeholder); 60 | start(); 61 | } 62 | 63 | @Override 64 | public void onResourceReady(Z resource, GlideAnimation animation) { 65 | cleanup(); 66 | super.onResourceReady(resource, animation); 67 | } 68 | 69 | @Override 70 | public void onLoadFailed(Exception e, Drawable errorDrawable) { 71 | cleanup(); 72 | super.onLoadFailed(e, errorDrawable); 73 | } 74 | 75 | @Override 76 | public void onLoadCleared(Drawable placeholder) { 77 | cleanup(); 78 | super.onLoadCleared(placeholder); 79 | } 80 | } -------------------------------------------------------------------------------- /imagereview/src/main/java/com/jchou/imagereview/glide/WrappingTarget.java: -------------------------------------------------------------------------------- 1 | package com.jchou.imagereview.glide; 2 | 3 | import android.graphics.drawable.Drawable; 4 | 5 | import com.bumptech.glide.request.Request; 6 | import com.bumptech.glide.request.animation.GlideAnimation; 7 | import com.bumptech.glide.request.target.SizeReadyCallback; 8 | import com.bumptech.glide.request.target.Target; 9 | 10 | public class WrappingTarget implements Target { 11 | protected final Target target; 12 | 13 | public WrappingTarget(Target target) { 14 | this.target = target; 15 | } 16 | 17 | @Override 18 | public void getSize(SizeReadyCallback cb) { 19 | if (target != null) 20 | target.getSize(cb); 21 | } 22 | 23 | @Override 24 | public void onLoadStarted(Drawable placeholder) { 25 | if (target != null) 26 | target.onLoadStarted(placeholder); 27 | } 28 | 29 | @Override 30 | public void onLoadFailed(Exception e, Drawable errorDrawable) { 31 | if (target != null) 32 | target.onLoadFailed(e, errorDrawable); 33 | } 34 | 35 | @Override 36 | public void onResourceReady(Z resource, GlideAnimation glideAnimation) { 37 | if (target != null) 38 | target.onResourceReady(resource, glideAnimation); 39 | } 40 | 41 | @Override 42 | public void onLoadCleared(Drawable placeholder) { 43 | if (target != null) target.onLoadCleared(placeholder); 44 | } 45 | 46 | private Request request; 47 | 48 | @Override 49 | public Request getRequest() { 50 | return request; 51 | } 52 | 53 | @Override 54 | public void setRequest(Request request) { 55 | this.request = request; 56 | if (target != null) 57 | target.setRequest(request); 58 | } 59 | 60 | @Override 61 | public void onStart() { 62 | if (target != null) 63 | target.onStart(); 64 | } 65 | 66 | @Override 67 | public void onStop() { 68 | if (target != null) 69 | target.onStop(); 70 | } 71 | 72 | @Override 73 | public void onDestroy() { 74 | if (target != null) target.onDestroy(); 75 | } 76 | } -------------------------------------------------------------------------------- /imagereview/src/main/java/com/jchou/imagereview/ui/ImageDetailFragment.java: -------------------------------------------------------------------------------- 1 | package com.jchou.imagereview.ui; 2 | 3 | import android.content.Context; 4 | import android.graphics.BitmapFactory; 5 | import android.graphics.PointF; 6 | import android.graphics.drawable.Drawable; 7 | import android.net.Uri; 8 | import android.os.Bundle; 9 | import android.support.annotation.Nullable; 10 | import android.support.v4.app.ActivityCompat; 11 | import android.support.v4.app.Fragment; 12 | import android.util.Log; 13 | import android.view.LayoutInflater; 14 | import android.view.View; 15 | import android.view.ViewGroup; 16 | import android.view.WindowManager; 17 | 18 | import com.bumptech.glide.Glide; 19 | import com.bumptech.glide.request.animation.GlideAnimation; 20 | import com.bumptech.glide.request.target.SizeReadyCallback; 21 | import com.bumptech.glide.request.target.Target; 22 | import com.davemorrissey.labs.subscaleview.ImageSource; 23 | import com.davemorrissey.labs.subscaleview.ImageViewState; 24 | import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView; 25 | import com.jchou.imagereview.R; 26 | import com.jchou.imagereview.glide.ProgressTarget; 27 | import com.jchou.imagereview.util.ScreenUtils; 28 | 29 | import java.io.File; 30 | 31 | import static com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.ZOOM_FOCUS_CENTER_IMMEDIATE; 32 | 33 | 34 | /** 35 | * 单张图片显示Fragment 36 | */ 37 | public class ImageDetailFragment extends Fragment { 38 | private String mImageUrl; 39 | 40 | private SubsamplingScaleImageView mImageView; 41 | 42 | 43 | private boolean isNewCreate = false, isVisible = false;//是否第一次加载完成,是否可见。 44 | 45 | public static ImageDetailFragment newInstance(String imageUrl) { 46 | final ImageDetailFragment f = new ImageDetailFragment(); 47 | 48 | final Bundle args = new Bundle(); 49 | args.putString("url", imageUrl); 50 | f.setArguments(args); 51 | 52 | return f; 53 | } 54 | 55 | 56 | @Override 57 | public void setUserVisibleHint(boolean isVisibleToUser) { 58 | super.setUserVisibleHint(isVisibleToUser); 59 | isVisible = isVisibleToUser; 60 | if (isVisibleToUser) { 61 | initData(); 62 | } else { 63 | isNewCreate = false; 64 | } 65 | } 66 | 67 | 68 | @Override 69 | public void onAttach(Context context) { 70 | super.onAttach(context); 71 | if(getActivity() instanceof OnLoadListener) { 72 | onLoadListener = (OnLoadListener)getActivity(); 73 | } 74 | } 75 | 76 | @Override 77 | public void onDetach() { 78 | super.onDetach(); 79 | onLoadListener=null; 80 | } 81 | 82 | @Override 83 | public void onCreate(Bundle savedInstanceState) { 84 | super.onCreate(savedInstanceState); 85 | Bundle bundle = getArguments(); 86 | mImageUrl = bundle != null ? bundle.getString("url", "") : ""; 87 | } 88 | 89 | @Override 90 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 91 | Bundle savedInstanceState) { 92 | final View v = inflater.inflate(R.layout.image_detail_fragment, 93 | container, false); 94 | mImageView = (SubsamplingScaleImageView) v.findViewById(R.id.image); 95 | mImageView.setMaxScale(15); 96 | mImageView.setZoomEnabled(true); 97 | mImageView.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CUSTOM); 98 | 99 | return v; 100 | } 101 | 102 | @Override 103 | public void onActivityCreated(@Nullable Bundle savedInstanceState) { 104 | super.onActivityCreated(savedInstanceState); 105 | isNewCreate = true;//布局新创建 106 | initData(); 107 | } 108 | 109 | 110 | private void initData() { 111 | if (!isVisible || !isNewCreate) { 112 | return; 113 | } 114 | if (onImageListener != null) { 115 | onImageListener.onInit(); 116 | } 117 | Glide.with(getActivity()) 118 | .load(mImageUrl) 119 | .downloadOnly(new ProgressTarget(mImageUrl, null) { 120 | 121 | @Override 122 | public void onLoadStarted(Drawable placeholder) { 123 | super.onLoadStarted(placeholder); 124 | if (onLoadListener!=null){ 125 | onLoadListener.onLoadStart(); 126 | } 127 | } 128 | 129 | @Override 130 | public void onProgress(long bytesRead, long expectedLength) { 131 | int p = 0; 132 | if (expectedLength >= 0) { 133 | p = (int) (100 * bytesRead / expectedLength); 134 | } 135 | } 136 | 137 | @Override 138 | public void onResourceReady(File resource, GlideAnimation animation) { 139 | // 将保存的图片地址给SubsamplingScaleImageView,这里注意设置ImageViewState设置初始显示比例 140 | ImageSource imageSource = ImageSource.uri(Uri.fromFile(resource)); 141 | int sWidth = BitmapFactory.decodeFile(resource.getAbsolutePath()).getWidth(); 142 | int sHeight = BitmapFactory.decodeFile(resource.getAbsolutePath()).getHeight(); 143 | WindowManager wm = (WindowManager) getActivity() 144 | .getSystemService(Context.WINDOW_SERVICE); 145 | int width = ScreenUtils.getScreenWidth(getActivity()); 146 | int height = ScreenUtils.getScreenHeight(getActivity()); 147 | float scaleW = width / (float) sWidth; 148 | float scaleH = height / (float) sHeight; 149 | if (sHeight >= height 150 | && sHeight / sWidth >= height / width) { 151 | mImageView.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CENTER_CROP); 152 | mImageView.setImage(ImageSource.uri(Uri.fromFile(resource)), new ImageViewState(scaleW, new PointF(0, 0), 0)); 153 | } else { 154 | mImageView.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CUSTOM); 155 | mImageView.setImage(ImageSource.uri(Uri.fromFile(resource))); 156 | mImageView.setDoubleTapZoomStyle(ZOOM_FOCUS_CENTER_IMMEDIATE); 157 | } 158 | ActivityCompat.startPostponedEnterTransition(getActivity()); 159 | if (onLoadListener!=null){ 160 | onLoadListener.onLoadSuccess(); 161 | } 162 | } 163 | 164 | @Override 165 | public void onLoadFailed(Exception e, Drawable errorDrawable) { 166 | ActivityCompat.startPostponedEnterTransition(getActivity()); 167 | if (onLoadListener!=null){ 168 | onLoadListener.onLoadFailed(); 169 | } 170 | } 171 | 172 | @Override 173 | public void getSize(SizeReadyCallback cb) { 174 | cb.onSizeReady(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL); 175 | } 176 | }); 177 | 178 | } 179 | 180 | 181 | public interface OnImageListener { 182 | void onInit(); 183 | } 184 | 185 | public void setOnImageListener(OnImageListener onImageListener) { 186 | this.onImageListener = onImageListener; 187 | } 188 | 189 | private OnImageListener onImageListener; 190 | 191 | 192 | public interface OnLoadListener{ 193 | void onLoadStart(); 194 | void onLoadSuccess(); 195 | void onLoadFailed(); 196 | } 197 | 198 | private OnLoadListener onLoadListener; 199 | 200 | @Override 201 | public void onDestroy() { 202 | super.onDestroy(); 203 | mImageView.recycle(); 204 | } 205 | 206 | 207 | } -------------------------------------------------------------------------------- /imagereview/src/main/java/com/jchou/imagereview/ui/ImagePagerActivity.java: -------------------------------------------------------------------------------- 1 | package com.jchou.imagereview.ui; 2 | 3 | 4 | import android.app.Activity; 5 | import android.content.Intent; 6 | import android.os.Bundle; 7 | import android.support.annotation.Nullable; 8 | import android.support.v4.app.ActivityCompat; 9 | import android.support.v4.app.ActivityOptionsCompat; 10 | import android.support.v4.app.SharedElementCallback; 11 | import android.support.v4.view.ViewPager; 12 | import android.support.v7.app.AppCompatActivity; 13 | import android.view.View; 14 | import android.view.Window; 15 | import android.view.WindowManager; 16 | import android.widget.ProgressBar; 17 | import android.widget.TextView; 18 | 19 | 20 | import com.jchou.imagereview.R; 21 | import com.jchou.imagereview.adapter.ImagePagerAdapter; 22 | import com.jchou.imagereview.widget.DragViewPager; 23 | 24 | import java.util.ArrayList; 25 | import java.util.List; 26 | import java.util.Map; 27 | 28 | 29 | /** 30 | * 图片查看器 31 | */ 32 | public class ImagePagerActivity extends AppCompatActivity implements ImageDetailFragment.OnLoadListener{ 33 | public static final String STATE_POSITION = "STATE_POSITION"; 34 | private static final String EXTRA_IMAGE_INDEX = "image_index"; 35 | private static final String EXTRA_IMAGE_URLS = "image_urls"; 36 | private ImagePagerAdapter mAdapter; 37 | private ArrayList mImgs; 38 | 39 | 40 | private DragViewPager mPager; 41 | private TextView indicator; 42 | private ProgressBar mProgress; 43 | 44 | private int pagerPosition; 45 | 46 | 47 | 48 | public static void startImagePage(Activity context, ArrayList urls, int pos, @Nullable View view) { 49 | Intent intent = new Intent(context, ImagePagerActivity.class); 50 | // 图片url,从数据库中或网络中获取 51 | intent.putExtra(EXTRA_IMAGE_URLS, urls); 52 | intent.putExtra(EXTRA_IMAGE_INDEX, pos); 53 | ActivityOptionsCompat compat; 54 | if (view == null) { 55 | compat = ActivityOptionsCompat.makeSceneTransitionAnimation(context); 56 | } else { 57 | compat = ActivityOptionsCompat.makeSceneTransitionAnimation(context, view, "img"); 58 | } 59 | ActivityCompat.startActivity(context, intent, compat.toBundle()); 60 | } 61 | 62 | @Override 63 | public void onCreate(Bundle savedInstanceState) { 64 | super.onCreate(savedInstanceState); 65 | // 隐藏标题栏 66 | requestWindowFeature(Window.FEATURE_NO_TITLE); 67 | // 隐藏状态栏 68 | getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, 69 | WindowManager.LayoutParams.FLAG_FULLSCREEN); 70 | //设置使用分享元素 71 | getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS); 72 | 73 | setContentView(R.layout.image_detail_page); 74 | 75 | pagerPosition = getIntent().getIntExtra(EXTRA_IMAGE_INDEX, 0); 76 | 77 | if (savedInstanceState != null) { 78 | pagerPosition = savedInstanceState.getInt(STATE_POSITION,0); 79 | } 80 | 81 | initView(); 82 | 83 | 84 | } 85 | 86 | private void initView() { 87 | mImgs = getIntent().getStringArrayListExtra( 88 | EXTRA_IMAGE_URLS); 89 | 90 | mPager = (DragViewPager) findViewById(R.id.pager); 91 | mPager.setIAnimClose(new DragViewPager.IAnimClose() { 92 | @Override 93 | public void onPictureClick() { 94 | transitionFinish(); 95 | } 96 | 97 | @Override 98 | public void onPictureRelease(View view) { 99 | transitionFinish(); 100 | } 101 | }); 102 | mPager.setOffscreenPageLimit(mImgs.size()); 103 | 104 | mAdapter = new ImagePagerAdapter(getSupportFragmentManager(), mImgs,mPager); 105 | // mPager.setAdapter(mAdapter); 106 | indicator = (TextView) findViewById(R.id.indicator); 107 | mProgress = (ProgressBar) findViewById(R.id.loadingIcon); 108 | 109 | CharSequence text = getString(R.string.viewpager_indicator, 1, mPager 110 | .getAdapter().getCount()); 111 | indicator.setText(text); 112 | // 更新下标 113 | mPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { 114 | 115 | @Override 116 | public void onPageScrollStateChanged(int arg0) { 117 | } 118 | 119 | @Override 120 | public void onPageScrolled(int arg0, float arg1, int arg2) { 121 | } 122 | 123 | @Override 124 | public void onPageSelected(int arg0) { 125 | CharSequence text = getString(R.string.viewpager_indicator, 126 | mPager.getCurrentItem() + 1, mPager.getAdapter().getCount()); 127 | indicator.setText(text); 128 | } 129 | 130 | }); 131 | mPager.setCurrentItem(pagerPosition); 132 | 133 | setEnterSharedElementCallback(new SharedElementCallback() { 134 | 135 | @Override 136 | public void onMapSharedElements(List names, Map sharedElements) { 137 | View view = mAdapter.getItem(mPager.getCurrentItem()).getView(); 138 | sharedElements.clear(); 139 | sharedElements.put("img", view); 140 | } 141 | }); 142 | 143 | } 144 | 145 | private void transitionFinish() { 146 | Intent intent=new Intent(); 147 | Bundle bundle=new Bundle(); 148 | bundle.putInt(STATE_POSITION,mPager.getCurrentItem()); 149 | intent.putExtras(bundle); 150 | setResult(RESULT_OK,intent); 151 | ActivityCompat.finishAfterTransition(this); 152 | } 153 | 154 | 155 | @Override 156 | public void onSaveInstanceState(Bundle outState) { 157 | outState.putInt(STATE_POSITION, mPager.getCurrentItem()); 158 | } 159 | 160 | 161 | @Override 162 | public void onBackPressed() { 163 | transitionFinish(); 164 | } 165 | 166 | 167 | @Override 168 | public void onLoadStart() { 169 | if (mProgress.getVisibility()!=View.VISIBLE) 170 | mProgress.setVisibility(View.VISIBLE); 171 | } 172 | 173 | @Override 174 | public void onLoadSuccess() { 175 | if (mProgress.getVisibility()==View.VISIBLE) 176 | mProgress.setVisibility(View.GONE); 177 | } 178 | 179 | @Override 180 | public void onLoadFailed() { 181 | if (mProgress.getVisibility()==View.VISIBLE) 182 | mProgress.setVisibility(View.GONE); 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /imagereview/src/main/java/com/jchou/imagereview/util/ScreenUtils.java: -------------------------------------------------------------------------------- 1 | package com.jchou.imagereview.util; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.graphics.Bitmap; 6 | import android.graphics.Rect; 7 | import android.util.DisplayMetrics; 8 | import android.view.View; 9 | import android.view.WindowManager; 10 | 11 | /** 12 | * 获得屏幕相关的辅助类 13 | * 14 | * @author zhy 15 | * 16 | */ 17 | public class ScreenUtils 18 | { 19 | private ScreenUtils() 20 | { 21 | /* cannot be instantiated */ 22 | throw new UnsupportedOperationException("cannot be instantiated"); 23 | } 24 | 25 | /** 26 | * 获得屏幕高度 27 | * 28 | * @param context 29 | * @return 30 | */ 31 | public static int getScreenWidth(Context context) 32 | { 33 | WindowManager wm = (WindowManager) context 34 | .getSystemService(Context.WINDOW_SERVICE); 35 | DisplayMetrics outMetrics = new DisplayMetrics(); 36 | wm.getDefaultDisplay().getMetrics(outMetrics); 37 | return outMetrics.widthPixels; 38 | } 39 | 40 | /** 41 | * 获得屏幕宽度 42 | * 43 | * @param context 44 | * @return 45 | */ 46 | public static int getScreenHeight(Context context) 47 | { 48 | WindowManager wm = (WindowManager) context 49 | .getSystemService(Context.WINDOW_SERVICE); 50 | DisplayMetrics outMetrics = new DisplayMetrics(); 51 | wm.getDefaultDisplay().getMetrics(outMetrics); 52 | return outMetrics.heightPixels; 53 | } 54 | 55 | /** 56 | * 获得状态栏的高度 57 | * 58 | * @param context 59 | * @return 60 | */ 61 | public static int getStatusHeight(Context context) 62 | { 63 | 64 | int statusHeight = -1; 65 | try 66 | { 67 | Class clazz = Class.forName("com.android.internal.R$dimen"); 68 | Object object = clazz.newInstance(); 69 | int height = Integer.parseInt(clazz.getField("status_bar_height") 70 | .get(object).toString()); 71 | statusHeight = context.getResources().getDimensionPixelSize(height); 72 | } catch (Exception e) 73 | { 74 | e.printStackTrace(); 75 | } 76 | return statusHeight; 77 | } 78 | 79 | /** 80 | * 获取当前屏幕截图,包含状态栏 81 | * 82 | * @param activity 83 | * @return 84 | */ 85 | public static Bitmap snapShotWithStatusBar(Activity activity) 86 | { 87 | View view = activity.getWindow().getDecorView(); 88 | view.setDrawingCacheEnabled(true); 89 | view.buildDrawingCache(); 90 | Bitmap bmp = view.getDrawingCache(); 91 | int width = getScreenWidth(activity); 92 | int height = getScreenHeight(activity); 93 | Bitmap bp = null; 94 | bp = Bitmap.createBitmap(bmp, 0, 0, width, height); 95 | view.destroyDrawingCache(); 96 | return bp; 97 | 98 | } 99 | 100 | /** 101 | * 获取当前屏幕截图,不包含状态栏 102 | * 103 | * @param activity 104 | * @return 105 | */ 106 | public static Bitmap snapShotWithoutStatusBar(Activity activity) 107 | { 108 | View view = activity.getWindow().getDecorView(); 109 | view.setDrawingCacheEnabled(true); 110 | view.buildDrawingCache(); 111 | Bitmap bmp = view.getDrawingCache(); 112 | Rect frame = new Rect(); 113 | activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(frame); 114 | int statusBarHeight = frame.top; 115 | 116 | int width = getScreenWidth(activity); 117 | int height = getScreenHeight(activity); 118 | Bitmap bp = null; 119 | bp = Bitmap.createBitmap(bmp, 0, statusBarHeight, width, height 120 | - statusBarHeight); 121 | view.destroyDrawingCache(); 122 | return bp; 123 | 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /imagereview/src/main/java/com/jchou/imagereview/widget/AdjustImageView.java: -------------------------------------------------------------------------------- 1 | package com.jchou.imagereview.widget; 2 | 3 | import android.content.Context; 4 | import android.graphics.drawable.Drawable; 5 | import android.support.v7.widget.AppCompatImageView; 6 | import android.util.AttributeSet; 7 | import android.view.MotionEvent; 8 | import android.view.ViewGroup; 9 | import android.view.ViewParent; 10 | import android.widget.Scroller; 11 | 12 | public class AdjustImageView extends AppCompatImageView { 13 | 14 | 15 | boolean mAdjustViewBounds;//是否维持原图比例 16 | 17 | public AdjustImageView(Context context) { 18 | super(context); 19 | } 20 | 21 | public AdjustImageView(Context context, AttributeSet attrs) { 22 | super(context, attrs); 23 | } 24 | 25 | public AdjustImageView(Context context, AttributeSet attrs, int defStyleAttr) { 26 | super(context, attrs, defStyleAttr); 27 | } 28 | 29 | @Override 30 | public void setAdjustViewBounds(boolean adjustViewBounds) { 31 | mAdjustViewBounds = adjustViewBounds; 32 | super.setAdjustViewBounds(adjustViewBounds); 33 | } 34 | 35 | @Override 36 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 37 | Drawable mDrawable = getDrawable(); 38 | if (mDrawable == null) { 39 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 40 | return; 41 | } 42 | 43 | if (mAdjustViewBounds) { 44 | int mDrawableWidth = mDrawable.getIntrinsicWidth(); 45 | int mDrawableHeight = mDrawable.getIntrinsicHeight(); 46 | int heightSize = MeasureSpec.getSize(heightMeasureSpec); 47 | int widthSize = MeasureSpec.getSize(widthMeasureSpec); 48 | int heightMode = MeasureSpec.getMode(heightMeasureSpec); 49 | int widthMode = MeasureSpec.getMode(widthMeasureSpec); 50 | 51 | if (heightMode == MeasureSpec.EXACTLY && widthMode != MeasureSpec.EXACTLY) { 52 | // Fixed Height & Adjustable Width 53 | int height = heightSize; 54 | int width = height * mDrawableWidth / mDrawableHeight; 55 | if (isInScrollingContainer()) 56 | setMeasuredDimension(width, height); 57 | else 58 | setMeasuredDimension(Math.min(width, widthSize), Math.min(height, heightSize)); 59 | } else if (widthMode == MeasureSpec.EXACTLY && heightMode != MeasureSpec.EXACTLY) { 60 | // Fixed Width & Adjustable Height 61 | int width = widthSize; 62 | int height = width * mDrawableHeight / mDrawableWidth; 63 | if (isInScrollingContainer()) { 64 | setMeasuredDimension(width, height); 65 | } else { 66 | setMeasuredDimension(Math.min(width, widthSize), Math.min(height, heightSize)); 67 | } 68 | } else { 69 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 70 | } 71 | } else { 72 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 73 | } 74 | } 75 | 76 | 77 | private boolean isInScrollingContainer() { 78 | ViewParent p = getParent(); 79 | while (p != null && p instanceof ViewGroup) { 80 | if (((ViewGroup) p).shouldDelayChildPressedState()) { 81 | return true; 82 | } 83 | p = p.getParent(); 84 | } 85 | return false; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /imagereview/src/main/java/com/jchou/imagereview/widget/DragViewPager.java: -------------------------------------------------------------------------------- 1 | package com.jchou.imagereview.widget; 2 | 3 | import android.content.Context; 4 | import android.graphics.Color; 5 | import android.support.v4.view.ViewPager; 6 | import android.util.AttributeSet; 7 | import android.util.Log; 8 | import android.view.MotionEvent; 9 | import android.view.VelocityTracker; 10 | import android.view.View; 11 | 12 | import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView; 13 | import com.jchou.imagereview.R; 14 | import com.jchou.imagereview.adapter.ImagePagerAdapter; 15 | import com.jchou.imagereview.util.ScreenUtils; 16 | import com.nineoldandroids.animation.ValueAnimator; 17 | import com.nineoldandroids.view.ViewHelper; 18 | 19 | /** 20 | * Created by Johnson on 2018/4/18. 21 | */ 22 | 23 | public class DragViewPager extends ViewPager implements View.OnClickListener { 24 | public static final int STATUS_NORMAL = 0;//正常浏览状态 25 | public static final int STATUS_MOVING = 1;//滑动状态 26 | public static final int STATUS_RESETTING = 2;//返回中状态 27 | public static final String TAG = "DragViewPager"; 28 | 29 | 30 | public static final float MIN_SCALE_SIZE = 0.3f;//最小缩放比例 31 | public static final int BACK_DURATION = 300;//ms 32 | public static final int DRAG_GAP_PX = 50; 33 | 34 | private int currentStatus = STATUS_NORMAL; 35 | private int currentPageStatus; 36 | 37 | private float mDownX; 38 | private float mDownY; 39 | private float screenHeight; 40 | 41 | /** 42 | * 要缩放的View 43 | */ 44 | private View currentShowView; 45 | /** 46 | * 滑动速度检测类 47 | */ 48 | private VelocityTracker mVelocityTracker; 49 | private IAnimClose iAnimClose; 50 | 51 | public void setIAnimClose(IAnimClose iAnimClose) { 52 | this.iAnimClose = iAnimClose; 53 | } 54 | 55 | public DragViewPager(Context context) { 56 | super(context); 57 | init(context); 58 | } 59 | 60 | public DragViewPager(Context context, AttributeSet attrs) { 61 | super(context, attrs); 62 | init(context); 63 | } 64 | 65 | public void init(Context context) { 66 | screenHeight = ScreenUtils.getScreenHeight(context); 67 | setBackgroundColor(Color.BLACK); 68 | addOnPageChangeListener(new OnPageChangeListener() { 69 | @Override 70 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 71 | } 72 | 73 | @Override 74 | public void onPageSelected(int position) { 75 | 76 | } 77 | 78 | @Override 79 | public void onPageScrollStateChanged(int state) { 80 | currentPageStatus = state; 81 | } 82 | }); 83 | } 84 | 85 | 86 | public void setCurrentShowView(View currentShowView) { 87 | this.currentShowView = currentShowView; 88 | if (this.currentShowView != null) { 89 | this.currentShowView.setOnClickListener(this); 90 | } 91 | } 92 | 93 | 94 | 95 | //配合SubsamplingScaleImageView使用,根据需要拦截ACTION_MOVE 96 | @Override 97 | public boolean onInterceptTouchEvent(MotionEvent ev) { 98 | if (getAdapter() instanceof ImagePagerAdapter) { 99 | ImagePagerAdapter adapter = ((ImagePagerAdapter) getAdapter()); 100 | SubsamplingScaleImageView mImage = (SubsamplingScaleImageView) adapter.getItem(getCurrentItem()).getView().findViewById(R.id.image); 101 | switch (ev.getAction()){ 102 | case MotionEvent.ACTION_DOWN: 103 | Log.e("jc","onInterceptTouchEvent:ACTION_DOWN"); 104 | mDownX = ev.getRawX(); 105 | mDownY = ev.getRawY(); 106 | break; 107 | case MotionEvent.ACTION_MOVE: 108 | Log.e("jc","onInterceptTouchEvent:ACTION_MOVE"); 109 | if (mImage.getCenter() != null && mImage.getCenter().y <= mImage.getHeight() / mImage.getScale() / 2) { 110 | Log.e("jc","onInterceptTouchEvent:ACTION_MOVE"); 111 | int deltaX = Math.abs((int) (ev.getRawX() - mDownX)); 112 | int deltaY = (int) (ev.getRawY() - mDownY); 113 | if (deltaY > DRAG_GAP_PX && deltaX <= DRAG_GAP_PX) {//往下移动超过临界,左右移动不超过临界时,拦截滑动事件 114 | return true; 115 | } 116 | } 117 | break; 118 | case MotionEvent.ACTION_UP: 119 | Log.e("jc","onInterceptTouchEvent:ACTION_UP"); 120 | break; 121 | } 122 | } 123 | return super.onInterceptTouchEvent(ev); 124 | } 125 | 126 | @Override 127 | public boolean onTouchEvent(MotionEvent ev) { 128 | if (currentStatus == STATUS_RESETTING) 129 | return false; 130 | switch (ev.getActionMasked()) { 131 | case MotionEvent.ACTION_DOWN: 132 | mDownX = ev.getRawX(); 133 | mDownY = ev.getRawY(); 134 | addIntoVelocity(ev); 135 | break; 136 | case MotionEvent.ACTION_MOVE: 137 | addIntoVelocity(ev); 138 | int deltaY = (int) (ev.getRawY() - mDownY); 139 | //手指往上滑动 140 | if (deltaY <= DRAG_GAP_PX && currentStatus != STATUS_MOVING) 141 | return super.onTouchEvent(ev); 142 | //viewpager不在切换中,并且手指往下滑动,开始缩放 143 | if (currentPageStatus != SCROLL_STATE_DRAGGING && (deltaY > DRAG_GAP_PX || currentStatus == STATUS_MOVING)) { 144 | moveView(ev.getRawX(), ev.getRawY()); 145 | return true; 146 | } 147 | break; 148 | case MotionEvent.ACTION_UP: 149 | case MotionEvent.ACTION_CANCEL: 150 | if (currentStatus != STATUS_MOVING) 151 | return super.onTouchEvent(ev); 152 | final float mUpX = ev.getRawX(); 153 | final float mUpY = ev.getRawY(); 154 | 155 | float vY = computeYVelocity();//松开时必须释放VelocityTracker资源 156 | if (vY >= 1200 || Math.abs(mUpY - mDownY) > screenHeight / 4) { 157 | //下滑速度快,或者下滑距离超过屏幕高度的一半,就关闭 158 | if (iAnimClose != null) { 159 | iAnimClose.onPictureRelease(currentShowView); 160 | } 161 | } else { 162 | resetReviewState(mUpX, mUpY); 163 | } 164 | break; 165 | } 166 | 167 | return super.onTouchEvent(ev); 168 | } 169 | 170 | //返回浏览状态 171 | private void resetReviewState(final float mUpX, final float mUpY) { 172 | currentStatus = STATUS_RESETTING; 173 | if (mUpY != mDownY) { 174 | ValueAnimator valueAnimator = ValueAnimator.ofFloat(mUpY, mDownY); 175 | valueAnimator.setDuration(BACK_DURATION); 176 | valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 177 | @Override 178 | public void onAnimationUpdate(ValueAnimator animation) { 179 | float mY = (float) animation.getAnimatedValue(); 180 | float percent = (mY - mDownY) / (mUpY - mDownY); 181 | float mX = percent * (mUpX - mDownX) + mDownX; 182 | moveView(mX, mY); 183 | if (mY == mDownY) { 184 | mDownY = 0; 185 | mDownX = 0; 186 | currentStatus = STATUS_NORMAL; 187 | } 188 | } 189 | }); 190 | valueAnimator.start(); 191 | } else if (mUpX != mDownX) { 192 | ValueAnimator valueAnimator = ValueAnimator.ofFloat(mUpX, mDownX); 193 | valueAnimator.setDuration(BACK_DURATION); 194 | valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 195 | @Override 196 | public void onAnimationUpdate(ValueAnimator animation) { 197 | float mX = (float) animation.getAnimatedValue(); 198 | float percent = (mX - mDownX) / (mUpX - mDownX); 199 | float mY = percent * (mUpY - mDownY) + mDownY; 200 | moveView(mX, mY); 201 | if (mX == mDownX) { 202 | mDownY = 0; 203 | mDownX = 0; 204 | currentStatus = STATUS_NORMAL; 205 | } 206 | } 207 | }); 208 | valueAnimator.start(); 209 | } else if (iAnimClose != null) 210 | iAnimClose.onPictureClick(); 211 | } 212 | 213 | 214 | //移动View 215 | private void moveView(float movingX, float movingY) { 216 | if (currentShowView == null) 217 | return; 218 | currentStatus = STATUS_MOVING; 219 | float deltaX = movingX - mDownX; 220 | float deltaY = movingY - mDownY; 221 | float scale = 1f; 222 | float alphaPercent = 1f; 223 | if (deltaY > 0) { 224 | scale = 1 - Math.abs(deltaY) / screenHeight; 225 | alphaPercent = 1 - Math.abs(deltaY) / (screenHeight / 2); 226 | } 227 | 228 | ViewHelper.setTranslationX(currentShowView, deltaX); 229 | ViewHelper.setTranslationY(currentShowView, deltaY); 230 | scaleView(scale); 231 | setBackgroundColor(getBlackAlpha(alphaPercent)); 232 | } 233 | 234 | //缩放View 235 | private void scaleView(float scale) { 236 | scale = Math.min(Math.max(scale, MIN_SCALE_SIZE), 1); 237 | ViewHelper.setScaleX(currentShowView, scale); 238 | ViewHelper.setScaleY(currentShowView, scale); 239 | } 240 | 241 | 242 | private int getBlackAlpha(float percent) { 243 | percent = Math.min(1, Math.max(0, percent)); 244 | int intAlpha = (int) (percent * 255); 245 | return Color.argb(intAlpha,0,0,0); 246 | } 247 | 248 | private void addIntoVelocity(MotionEvent event) { 249 | if (mVelocityTracker == null) 250 | mVelocityTracker = VelocityTracker.obtain(); 251 | mVelocityTracker.addMovement(event); 252 | } 253 | 254 | 255 | private float computeYVelocity() { 256 | float result = 0; 257 | if (mVelocityTracker != null) { 258 | mVelocityTracker.computeCurrentVelocity(1000); 259 | result = mVelocityTracker.getYVelocity(); 260 | releaseVelocity(); 261 | } 262 | return result; 263 | } 264 | 265 | private void releaseVelocity() { 266 | if (mVelocityTracker != null) { 267 | mVelocityTracker.clear(); 268 | mVelocityTracker.recycle(); 269 | mVelocityTracker = null; 270 | } 271 | } 272 | 273 | @Override 274 | public void onClick(View v) { 275 | if (iAnimClose != null) { 276 | iAnimClose.onPictureClick(); 277 | } 278 | } 279 | 280 | 281 | public interface IAnimClose { 282 | void onPictureClick(); 283 | 284 | void onPictureRelease(View view); 285 | } 286 | 287 | 288 | } 289 | -------------------------------------------------------------------------------- /imagereview/src/main/res/layout/image_detail_fragment.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | -------------------------------------------------------------------------------- /imagereview/src/main/res/layout/image_detail_page.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 11 | 12 | 13 | 23 | 24 | 25 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /imagereview/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnsonHou/ImageReview/7b882eb0a2463d128e6c667428a3810e67d37424/imagereview/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /imagereview/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnsonHou/ImageReview/7b882eb0a2463d128e6c667428a3810e67d37424/imagereview/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /imagereview/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnsonHou/ImageReview/7b882eb0a2463d128e6c667428a3810e67d37424/imagereview/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /imagereview/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnsonHou/ImageReview/7b882eb0a2463d128e6c667428a3810e67d37424/imagereview/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /imagereview/src/main/res/mipmap-mdpi/refresh_loading01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnsonHou/ImageReview/7b882eb0a2463d128e6c667428a3810e67d37424/imagereview/src/main/res/mipmap-mdpi/refresh_loading01.png -------------------------------------------------------------------------------- /imagereview/src/main/res/mipmap-mdpi/refresh_loading02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnsonHou/ImageReview/7b882eb0a2463d128e6c667428a3810e67d37424/imagereview/src/main/res/mipmap-mdpi/refresh_loading02.png -------------------------------------------------------------------------------- /imagereview/src/main/res/mipmap-mdpi/refresh_loading03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnsonHou/ImageReview/7b882eb0a2463d128e6c667428a3810e67d37424/imagereview/src/main/res/mipmap-mdpi/refresh_loading03.png -------------------------------------------------------------------------------- /imagereview/src/main/res/mipmap-mdpi/refresh_loading04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnsonHou/ImageReview/7b882eb0a2463d128e6c667428a3810e67d37424/imagereview/src/main/res/mipmap-mdpi/refresh_loading04.png -------------------------------------------------------------------------------- /imagereview/src/main/res/mipmap-mdpi/refresh_loading05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnsonHou/ImageReview/7b882eb0a2463d128e6c667428a3810e67d37424/imagereview/src/main/res/mipmap-mdpi/refresh_loading05.png -------------------------------------------------------------------------------- /imagereview/src/main/res/mipmap-mdpi/refresh_loading06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnsonHou/ImageReview/7b882eb0a2463d128e6c667428a3810e67d37424/imagereview/src/main/res/mipmap-mdpi/refresh_loading06.png -------------------------------------------------------------------------------- /imagereview/src/main/res/mipmap-mdpi/refresh_loading07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnsonHou/ImageReview/7b882eb0a2463d128e6c667428a3810e67d37424/imagereview/src/main/res/mipmap-mdpi/refresh_loading07.png -------------------------------------------------------------------------------- /imagereview/src/main/res/mipmap-mdpi/refresh_loading08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnsonHou/ImageReview/7b882eb0a2463d128e6c667428a3810e67d37424/imagereview/src/main/res/mipmap-mdpi/refresh_loading08.png -------------------------------------------------------------------------------- /imagereview/src/main/res/mipmap-mdpi/refresh_loading09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnsonHou/ImageReview/7b882eb0a2463d128e6c667428a3810e67d37424/imagereview/src/main/res/mipmap-mdpi/refresh_loading09.png -------------------------------------------------------------------------------- /imagereview/src/main/res/mipmap-mdpi/refresh_loading10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnsonHou/ImageReview/7b882eb0a2463d128e6c667428a3810e67d37424/imagereview/src/main/res/mipmap-mdpi/refresh_loading10.png -------------------------------------------------------------------------------- /imagereview/src/main/res/mipmap-mdpi/refresh_loading11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnsonHou/ImageReview/7b882eb0a2463d128e6c667428a3810e67d37424/imagereview/src/main/res/mipmap-mdpi/refresh_loading11.png -------------------------------------------------------------------------------- /imagereview/src/main/res/mipmap-mdpi/refresh_loading12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnsonHou/ImageReview/7b882eb0a2463d128e6c667428a3810e67d37424/imagereview/src/main/res/mipmap-mdpi/refresh_loading12.png -------------------------------------------------------------------------------- /imagereview/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnsonHou/ImageReview/7b882eb0a2463d128e6c667428a3810e67d37424/imagereview/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /imagereview/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnsonHou/ImageReview/7b882eb0a2463d128e6c667428a3810e67d37424/imagereview/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /imagereview/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnsonHou/ImageReview/7b882eb0a2463d128e6c667428a3810e67d37424/imagereview/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /imagereview/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnsonHou/ImageReview/7b882eb0a2463d128e6c667428a3810e67d37424/imagereview/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /imagereview/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnsonHou/ImageReview/7b882eb0a2463d128e6c667428a3810e67d37424/imagereview/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /imagereview/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnsonHou/ImageReview/7b882eb0a2463d128e6c667428a3810e67d37424/imagereview/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /imagereview/src/main/res/values/color.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #ffffffff 4 | #ffffffff 5 | #ffffffff 6 | -------------------------------------------------------------------------------- /imagereview/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /imagereview/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | ImageReview 3 | %1$d/%2$d 4 | 5 | -------------------------------------------------------------------------------- /imagereview/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 13 | 19 | 20 | 21 | 28 | -------------------------------------------------------------------------------- /imagereview/src/test/java/com/jchou/imagereview/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.jchou.imagereview; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':imagereview' 2 | --------------------------------------------------------------------------------