├── .gitignore ├── .idea ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml ├── inspectionProfiles │ ├── Project_Default.xml │ └── profiles_settings.xml ├── misc.xml ├── modules.xml └── runConfigurations.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── demo │ │ └── imagetransdemo │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ └── data.json │ ├── java │ │ └── com │ │ │ └── demo │ │ │ └── imagetransdemo │ │ │ ├── ItemData.java │ │ │ ├── MathUtils.java │ │ │ ├── MyApplication.java │ │ │ ├── TileBitmapDrawable.java │ │ │ ├── activity │ │ │ ├── MainActivity.java │ │ │ ├── NetImageActivity.java │ │ │ ├── PhotoAlbumActivity.java │ │ │ └── TimeLineActivity.java │ │ │ ├── adapter │ │ │ ├── CustomTransform.java │ │ │ ├── MyImageLoad.java │ │ │ ├── MyImageTransAdapter.java │ │ │ ├── MyProgressBarGet.java │ │ │ └── TimeLineAdapter.java │ │ │ ├── imageload │ │ │ ├── OkHttpImageLoad.java │ │ │ └── Platform.java │ │ │ └── view │ │ │ ├── DragParentView.java │ │ │ ├── ItemDecorationAlbumColumns.java │ │ │ ├── RingLoadingView.java │ │ │ ├── RoundPageIndicator.java │ │ │ └── SpaceDecoration.java │ └── res │ │ ├── animator │ │ └── scale_with_alpha.xml │ │ ├── drawable │ │ ├── button.xml │ │ ├── ic_arrow_down.xml │ │ ├── ic_close.xml │ │ ├── ic_like.xml │ │ ├── ic_more.xml │ │ ├── ic_replay.xml │ │ ├── ic_repost.xml │ │ ├── place_holder.xml │ │ └── white_radius.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── activity_photo_album.xml │ │ ├── activity_time_line.xml │ │ ├── image_trans_adapter.xml │ │ ├── item_header.xml │ │ ├── item_single_image.xml │ │ ├── item_single_images.xml │ │ └── item_toolbar.xml │ │ ├── menu │ │ ├── menu_photo.xml │ │ └── menu_scale_type.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── avatar.jpg │ │ ├── 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 │ └── demo │ └── imagetransdemo │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── imagetrans ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── it │ │ └── liuting │ │ └── imagetrans │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── it │ │ │ └── liuting │ │ │ └── imagetrans │ │ │ ├── Compat.java │ │ │ ├── CustomGestureDetector.java │ │ │ ├── DialogView.java │ │ │ ├── ITConfig.java │ │ │ ├── ImageGesturesAttacher.java │ │ │ ├── ImageItemView.java │ │ │ ├── ImageLoad.java │ │ │ ├── ImageTrans.java │ │ │ ├── ImageTransAdapter.java │ │ │ ├── ImageTransBuild.java │ │ │ ├── InterceptViewPager.java │ │ │ ├── ScaleType.java │ │ │ ├── ThumbConfig.java │ │ │ ├── TransImageView.java │ │ │ ├── TransformAttacher.java │ │ │ ├── Util.java │ │ │ ├── evaluator │ │ │ ├── MatrixEvaluator.java │ │ │ └── RectFEvaluator.java │ │ │ └── listener │ │ │ ├── OnActionListener.java │ │ │ ├── OnGestureListener.java │ │ │ ├── OnPullCloseListener.java │ │ │ ├── OnTransformListener.java │ │ │ ├── ProgressViewGet.java │ │ │ └── SourceImageViewGet.java │ └── res │ │ ├── layout │ │ ├── image_fragment.xml │ │ └── images_viewpager.xml │ │ ├── values-v19 │ │ └── styles.xml │ │ ├── values-v21 │ │ └── styles.xml │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── it │ └── liuting │ └── imagetrans │ └── ExampleUnitTest.java ├── preview ├── app-debug.apk ├── preview_1.gif ├── preview_2.gif └── preview_3.gif └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | /.idea/gradle.xml 6 | /.idea/workspace.xml 7 | /.idea/libraries 8 | .DS_Store 9 | /build 10 | /captures 11 | .externalNativeBuild 12 | /imagetrans/build 13 | -------------------------------------------------------------------------------- /.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/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | Android 36 | 37 | 38 | Android > Lint > Correctness 39 | 40 | 41 | Java 42 | 43 | 44 | Java language level migration aidsJava 45 | 46 | 47 | 48 | 49 | Android 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 61 | 62 | 63 | 64 | 65 | 1.8 66 | 67 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ImageTrans 2 | 简单的封装了一个仿微信的图片查看过渡动画demo
3 | * 1.支持4种裁剪方式(CENTER_CROP、START_CROP、END_CROP、FIT_XY)的缩略图到原图的无缝过渡(支持长图) 4 | * 2.仿照微信的下拉手势关闭图片 5 | ##### 具体demo 请下载:[demo.apk](preview/app-debug.apk)
6 | ## 预览 7 | 根据四种缩略图裁剪方式变形动画
8 | 9 | 10 | 11 | 12 | ## 使用方法 13 | ``` 14 | ImageTrans.with(context) 15 | .setImageList(imageList) 16 | .setNowIndex(clickPos) 17 | .setSourceImageView(new SourceImageViewGet()) 18 | .setProgressBar(new ProgressViewGet()) 19 | .setImageLoad(new ImageLoad()) 20 | .setAdapter(new ImageTransAdapter()) 21 | .show(); 22 | ``` 23 | ### 可选设置 24 | ``` 25 | setConfig(new ITConfig()) 26 | ``` 27 | |方法|说明| 28 | |:---|:---| 29 | |enableReadMode(boolean)|是否开启阅读模式,针对长图默认适宽显示 | 30 | |largeThumb()| 预览图适宽显示,默认父容器宽度的1/2显示 | 31 | |readModeRule(float)|自定义长图的判断标准,默认视图高度的1.5倍| 32 | |noThumbWhenCached()|当有缓存的时候不显示预览图,直接显示原图 | 33 | |noThumb()|不显示预览图 | 34 | 35 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "25.0.3" 6 | defaultConfig { 7 | applicationId "com.demo.imagetransdemo" 8 | minSdkVersion 17 9 | targetSdkVersion 25 10 | versionCode 1 11 | versionName "1.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(dir: 'libs', include: ['*.jar']) 24 | compile project(':imagetrans') 25 | compile 'com.android.support:appcompat-v7:25.3.1' 26 | compile "com.android.support:design:25.3.1" 27 | compile('com.github.bumptech.glide:glide:3.7.0') { 28 | exclude module: 'support-v4' 29 | } 30 | debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.4' 31 | releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4' 32 | compile 'com.squareup.okhttp:okhttp-urlconnection:2.4.0' 33 | compile 'com.github.hotchemi:permissionsdispatcher:2.1.3' 34 | // compile 'io.supercharge:shimmerlayout:0.8.0' 35 | annotationProcessor 'com.github.hotchemi:permissionsdispatcher-processor:2.1.3' 36 | } 37 | -------------------------------------------------------------------------------- /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 /Applications/AndroidStudio2.app/Contents/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/demo/imagetransdemo/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.demo.imagetransdemo; 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.demo.imagetransdemo", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/assets/data.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "text": "深海之蓝!!!!!", 4 | "images": [ 5 | "http://wx3.sinaimg.cn/woriginal/0067tVFLly1fgfdpx2kxrj31401z4qil.jpg" 6 | ] 7 | }, 8 | { 9 | "text": "【小清新风光的场景唯美插画图片】“期待万物复苏的春天,好像可以给自己一个理由,扔掉一切糟糕的情绪,拍一拍身上的尘土,继续笑着向前。”让他带你慢慢入梦! ", 10 | "images": [ 11 | "https://wx2.sinaimg.cn/large/abbc7556ly1ffme8puc8sj20h8559npd.jpg" 12 | ] 13 | }, 14 | { 15 | "text": "#情话# #壁纸# 经典爱情伤感文字唯美带字风景图片。我不愿让你一个人,一个人在人海浮沉。与我相拥,附着一句抱歉,近来的打扰。宁愿笑着流泪,也不要哭着说后悔。一段感情不管当初有多开心,分手时就有多难过,一组爱情伤感文字分享。 ", 16 | "images": [ 17 | "http://wx1.sinaimg.cn/woriginal/daaf97d2gy1fgsxkq8uc3j20dw0ku74x.jpg", 18 | "http://wx1.sinaimg.cn/woriginal/daaf97d2gy1fgsxkqm7b0j20dw0kut9h.jpg", 19 | "http://wx4.sinaimg.cn/woriginal/daaf97d2gy1fgsxks2l4ij20dw0kldhb.jpg", 20 | "http://wx2.sinaimg.cn/woriginal/daaf97d2gy1fgsxksskbkj20dw0kut9b.jpg" 21 | ] 22 | }, 23 | { 24 | "text": "用心点的关爱!!!", 25 | "images": [ 26 | "http://wx1.sinaimg.cn/bmiddle/9672f95cly1fgcl0xc7hmj20gt5fwhdt.jpg" 27 | ] 28 | }, 29 | { 30 | "text": "【雪莉给烤鳗鱼配音再被批 回应:你们更可恶】20日, #崔雪莉#分享一段烤鳗鱼视频→-微博视频 视频中,烤盘中的鳗鱼头还在动,雪莉在一旁配音\"啊~救救我~\"不料却遭网友批评,称鳗鱼还活着,雪莉笑着配音的做法过于残忍。随后,雪莉发文回应:“你们别吃鳗鱼。”并评论道“你们更可恶!", 31 | "images": [ 32 | "http://wx1.sinaimg.cn/woriginal/61e7f4aaly1fgrt0bj3htj20gg0c7myr.jpg", 33 | "http://wx4.sinaimg.cn/woriginal/61e7f4aaly1fgrt0bpvkxj20go080wg9.jpg", 34 | "http://wx2.sinaimg.cn/woriginal/61e7f4aaly1fgrt0bcfhpj20u011hwha.jpg", 35 | "http://wx3.sinaimg.cn/woriginal/61e7f4aaly1fgrt0agnqyj20p00v9diq.jpg", 36 | "http://wx2.sinaimg.cn/woriginal/61e7f4aaly1fgrt0apox0j20u011idjh.jpg", 37 | "http://wx4.sinaimg.cn/woriginal/61e7f4aaly1fgrt0b0ww9j20u011iwj9.jpg" 38 | ] 39 | }, 40 | { 41 | "text": "当我觉得我倒霉了,我就会想:这是在挥洒我的霉运,挥洒之后,剩下的就全都是幸运了!(图片来自日推画师katahi0829) ", 42 | "images": [ 43 | "http://ww3.sinaimg.cn/woriginal/75d91745gw1f0echzpyx6j20sg13pqg3.jpg", 44 | "http://ww4.sinaimg.cn/woriginal/75d91745gw1f0eci2262gj20sg14bh14.jpg", 45 | "http://ww3.sinaimg.cn/woriginal/75d91745gw1f0eci5qk5kj20sg12bdt3.jpg", 46 | "http://ww1.sinaimg.cn/woriginal/75d91745gw1f0eci7jn0mj20sg0k80wq.jpg", 47 | "http://ww2.sinaimg.cn/woriginal/75d91745gw1f0eci3mwbqj20go0nbwkd.jpg", 48 | "http://ww3.sinaimg.cn/woriginal/75d91745gw1f0eci9sybpj20sg14enbf.jpg", 49 | "http://ww3.sinaimg.cn/woriginal/75d91745gw1f0ecic7g0ej20sg0k0wnd.jpg", 50 | "http://ww2.sinaimg.cn/woriginal/75d91745gw1f0ecif04ocj20sg14mqlb.jpg", 51 | "http://ww1.sinaimg.cn/woriginal/75d91745gw1f0ecigzhl9j20sg138gz4.jpg" 52 | ] 53 | } 54 | ] -------------------------------------------------------------------------------- /app/src/main/java/com/demo/imagetransdemo/ItemData.java: -------------------------------------------------------------------------------- 1 | package com.demo.imagetransdemo; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | /** 7 | * Created by liuting on 17/6/20. 8 | */ 9 | 10 | public class ItemData { 11 | public static final int VIEW_TYPE_SINGLE = 0; 12 | public static final int VIEW_TYPE_MUTIL = 1; 13 | public List images = new ArrayList<>(); 14 | public String text; 15 | 16 | public int getViewType() { 17 | if (images.size() == 1) { 18 | return VIEW_TYPE_SINGLE; 19 | } else { 20 | return VIEW_TYPE_MUTIL; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/demo/imagetransdemo/MathUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.demo.imagetransdemo; 18 | 19 | public class MathUtils { 20 | 21 | public static int ceilLog2(float value) { 22 | int i; 23 | for (i = 0; i < 31; i++) { 24 | if ((1 << i) >= value) break; 25 | } 26 | return i; 27 | } 28 | 29 | public static int floorLog2(float value) { 30 | int i; 31 | for (i = 0; i < 31; i++) { 32 | if ((1 << i) > value) break; 33 | } 34 | return i - 1; 35 | } 36 | 37 | // Returns the input value x clamped to the range [min, max]. 38 | public static int clamp(int x, int min, int max) { 39 | if (x > max) return max; 40 | if (x < min) return min; 41 | return x; 42 | } 43 | 44 | // Returns the input value x clamped to the range [min, max]. 45 | public static float clamp(float x, float min, float max) { 46 | if (x > max) return max; 47 | if (x < min) return min; 48 | return x; 49 | } 50 | 51 | // Returns the input value x clamped to the range [min, max]. 52 | public static long clamp(long x, long min, long max) { 53 | if (x > max) return max; 54 | if (x < min) return min; 55 | return x; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/src/main/java/com/demo/imagetransdemo/MyApplication.java: -------------------------------------------------------------------------------- 1 | package com.demo.imagetransdemo; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | import android.graphics.BitmapFactory; 6 | import android.util.DisplayMetrics; 7 | 8 | import com.bumptech.glide.Glide; 9 | import com.squareup.leakcanary.LeakCanary; 10 | 11 | import java.io.BufferedReader; 12 | import java.io.File; 13 | import java.io.InputStreamReader; 14 | import java.math.BigInteger; 15 | import java.security.MessageDigest; 16 | import java.security.NoSuchAlgorithmException; 17 | import java.util.concurrent.ExecutorService; 18 | import java.util.concurrent.Executors; 19 | 20 | /** 21 | * Created by liuting on 17/6/1. 22 | */ 23 | 24 | public class MyApplication extends Application { 25 | private static final String HASH_ALGORITHM = "MD5"; 26 | private static final int RADIX = 10 + 26; // 10 digits + 26 letters 27 | 28 | static DisplayMetrics mDisplayMetrics; 29 | public static ExecutorService cThreadPool; 30 | private static String IMAGE_CACHE_PATH; 31 | 32 | 33 | @Override 34 | public void onCreate() { 35 | super.onCreate(); 36 | if (LeakCanary.isInAnalyzerProcess(this)) return; 37 | LeakCanary.install(this); 38 | mDisplayMetrics = getResources().getDisplayMetrics(); 39 | cThreadPool = Executors.newFixedThreadPool(5); 40 | IMAGE_CACHE_PATH = getExternalCacheDir().getPath(); 41 | } 42 | 43 | public static int dpToPx(int dp) { 44 | return (int) (mDisplayMetrics.density * dp); 45 | } 46 | 47 | public static int getScreenWidth() { 48 | return mDisplayMetrics.widthPixels; 49 | } 50 | 51 | public static int getScreenHeight() { 52 | return mDisplayMetrics.heightPixels; 53 | } 54 | 55 | public static void deleteFiles(String path) { 56 | deleteFiles(new File(path)); 57 | } 58 | 59 | public static void deleteFiles(File file) { 60 | if (!file.exists()) { 61 | return; 62 | } 63 | if (file.isFile()) { 64 | file.delete(); 65 | return; 66 | } 67 | //文件夹递归删除 68 | File[] files = file.listFiles(); 69 | if (null == files) { 70 | return; 71 | } 72 | for (File subFile : files) { 73 | deleteFiles(subFile); 74 | } 75 | file.delete(); 76 | } 77 | 78 | public static String getImageCachePath() { 79 | return IMAGE_CACHE_PATH; 80 | } 81 | 82 | public static int getMaxSizeOfBitMap(String path) { 83 | BitmapFactory.Options op = new BitmapFactory.Options(); 84 | op.inJustDecodeBounds = true; 85 | BitmapFactory.decodeFile(path, op); 86 | return Math.max(op.outWidth, op.outHeight); 87 | } 88 | 89 | private static byte[] getMD5(byte[] data) { 90 | byte[] hash = null; 91 | try { 92 | MessageDigest digest = MessageDigest.getInstance(HASH_ALGORITHM); 93 | digest.update(data); 94 | hash = digest.digest(); 95 | } catch (NoSuchAlgorithmException e) { 96 | } 97 | return hash; 98 | } 99 | 100 | public static String generate(String imageUri) { 101 | byte[] md5 = getMD5(imageUri.getBytes()); 102 | BigInteger bi = new BigInteger(md5).abs(); 103 | if (imageUri.endsWith(".gif") || imageUri.endsWith(".GIF")) { 104 | return bi.toString(RADIX) + ".itgif"; 105 | } 106 | return bi.toString(RADIX) + ".it"; 107 | } 108 | 109 | public static void clearCache(final Context context) { 110 | cThreadPool.submit(new Runnable() { 111 | @Override 112 | public void run() { 113 | File file = new File(IMAGE_CACHE_PATH); 114 | File[] files = file.listFiles(); 115 | for (File item : files) { 116 | item.delete(); 117 | } 118 | Glide.get(context).clearDiskCache(); 119 | } 120 | }); 121 | 122 | } 123 | 124 | public static String getFromAssets(Context context, String fileName) { 125 | try { 126 | InputStreamReader inputReader = new InputStreamReader(context.getResources().getAssets().open(fileName)); 127 | BufferedReader bufReader = new BufferedReader(inputReader); 128 | String line = ""; 129 | String Result = ""; 130 | while ((line = bufReader.readLine()) != null) 131 | Result += line; 132 | return Result; 133 | } catch (Exception e) { 134 | e.printStackTrace(); 135 | } 136 | return ""; 137 | } 138 | 139 | public static String getCachedPath(String url) { 140 | String key = generate(url); 141 | String destUrl = getImageCachePath() + "/" + key; 142 | return destUrl; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /app/src/main/java/com/demo/imagetransdemo/activity/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.demo.imagetransdemo.activity; 2 | 3 | import android.Manifest; 4 | import android.content.DialogInterface; 5 | import android.content.Intent; 6 | import android.os.Bundle; 7 | import android.support.annotation.NonNull; 8 | import android.support.v7.app.AlertDialog; 9 | import android.support.v7.app.AppCompatActivity; 10 | import android.support.v7.widget.AppCompatSeekBar; 11 | import android.support.v7.widget.Toolbar; 12 | import android.view.Menu; 13 | import android.view.MenuItem; 14 | import android.view.View; 15 | import android.widget.Button; 16 | import android.widget.ImageView; 17 | import android.widget.SeekBar; 18 | import android.widget.Toast; 19 | 20 | import com.bumptech.glide.Glide; 21 | import com.demo.imagetransdemo.MyApplication; 22 | import com.demo.imagetransdemo.R; 23 | import com.demo.imagetransdemo.adapter.CustomTransform; 24 | import com.demo.imagetransdemo.adapter.MyImageLoad; 25 | import com.demo.imagetransdemo.view.DragParentView; 26 | 27 | import java.util.ArrayList; 28 | import java.util.List; 29 | 30 | import it.liuting.imagetrans.ITConfig; 31 | import it.liuting.imagetrans.ImageTrans; 32 | import it.liuting.imagetrans.ScaleType; 33 | import it.liuting.imagetrans.listener.SourceImageViewGet; 34 | import permissions.dispatcher.NeedsPermission; 35 | import permissions.dispatcher.OnPermissionDenied; 36 | import permissions.dispatcher.OnShowRationale; 37 | import permissions.dispatcher.PermissionRequest; 38 | import permissions.dispatcher.RuntimePermissions; 39 | 40 | @RuntimePermissions 41 | public class MainActivity extends AppCompatActivity implements SeekBar.OnSeekBarChangeListener { 42 | public static final int DEFAULT_MIN_SIZE = MyApplication.dpToPx(100); 43 | Toolbar toolbar; 44 | DragParentView dragParentView; 45 | ImageView imageView; 46 | AppCompatSeekBar widthSeekBar; 47 | AppCompatSeekBar heightSeekBar; 48 | Button centerCrop; 49 | Button startCrop; 50 | Button endCrop; 51 | Button fitXy; 52 | int maxSize = MyApplication.getScreenWidth(); 53 | ScaleType scaleType = ScaleType.CENTER_CROP; 54 | String drawableId = "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1496996959&di=13c094ba73675a24df2ad1d2c730c02c&imgtype=jpg&er=1&src=http%3A%2F%2Fdasouji.com%2Fwp-content%2Fuploads%2F2015%2F07%2F%25E9%2595%25BF%25E8%258A%25B1%25E5%259B%25BE-6.jpg"; 55 | 56 | @Override 57 | protected void onCreate(Bundle savedInstanceState) { 58 | super.onCreate(savedInstanceState); 59 | MainActivityPermissionsDispatcher.initPermissionWithCheck(this); 60 | setContentView(R.layout.activity_main); 61 | toolbar = (Toolbar) findViewById(R.id.toolbar); 62 | setSupportActionBar(toolbar); 63 | dragParentView = (DragParentView) findViewById(R.id.drag_parent_view); 64 | widthSeekBar = (AppCompatSeekBar) findViewById(R.id.seek_bar_width); 65 | heightSeekBar = (AppCompatSeekBar) findViewById(R.id.seek_bar_height); 66 | centerCrop = (Button) findViewById(R.id.center_crop); 67 | startCrop = (Button) findViewById(R.id.top_crop); 68 | endCrop = (Button) findViewById(R.id.bottom_crop); 69 | fitXy = (Button) findViewById(R.id.fix_xy); 70 | centerCrop.setSelected(true); 71 | widthSeekBar.setOnSeekBarChangeListener(this); 72 | heightSeekBar.setOnSeekBarChangeListener(this); 73 | widthSeekBar.setMax(maxSize); 74 | heightSeekBar.setMax(maxSize); 75 | imageView = new ImageView(this); 76 | dragParentView.getLayoutParams().height = MyApplication.getScreenWidth(); 77 | dragParentView.setLayoutParams(dragParentView.getLayoutParams()); 78 | dragParentView.setView(imageView, DEFAULT_MIN_SIZE, DEFAULT_MIN_SIZE); 79 | imageView.setScaleType(ImageView.ScaleType.CENTER_CROP); 80 | final List images = new ArrayList<>(); 81 | images.add(drawableId); 82 | imageView.setOnClickListener(new View.OnClickListener() { 83 | @Override 84 | public void onClick(View v) { 85 | ImageTrans.with(MainActivity.this) 86 | .setImageList(images) 87 | .setScaleType(scaleType) 88 | .setSourceImageView(new SourceImageViewGet() { 89 | @Override 90 | public ImageView getImageView(int pos) { 91 | return imageView; 92 | } 93 | }) 94 | .setImageLoad(new MyImageLoad()) 95 | .setConfig(new ITConfig().noThumbWhenCached()) 96 | .show(); 97 | } 98 | }); 99 | loadImage(); 100 | } 101 | 102 | @Override 103 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 104 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 105 | MainActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults); 106 | } 107 | 108 | @NeedsPermission({Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE}) 109 | void initPermission() { 110 | 111 | } 112 | 113 | @OnShowRationale({Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE}) 114 | void showRationale(final PermissionRequest request) { 115 | new AlertDialog.Builder(this) 116 | .setTitle("请求权限") 117 | .setMessage("需要SD卡读写权限") 118 | .setPositiveButton("确定", new DialogInterface.OnClickListener() { 119 | @Override 120 | public void onClick(DialogInterface dialog, int which) { 121 | request.proceed(); 122 | } 123 | }) 124 | .setNegativeButton("取消", new DialogInterface.OnClickListener() { 125 | @Override 126 | public void onClick(DialogInterface dialog, int which) { 127 | request.cancel(); 128 | } 129 | }).show(); 130 | } 131 | 132 | @OnPermissionDenied({Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE}) 133 | void DeniedPermission() { 134 | Toast.makeText(this, "SD卡读写权限被禁止", Toast.LENGTH_LONG).show(); 135 | finish(); 136 | } 137 | 138 | @Override 139 | public boolean onCreateOptionsMenu(Menu menu) { 140 | getMenuInflater().inflate(R.menu.menu_photo, menu); 141 | return super.onCreateOptionsMenu(menu); 142 | } 143 | 144 | @Override 145 | public boolean onOptionsItemSelected(MenuItem item) { 146 | switch (item.getItemId()) { 147 | case R.id.action_local_album: { 148 | Intent intent = new Intent(MainActivity.this, PhotoAlbumActivity.class); 149 | startActivity(intent); 150 | break; 151 | } 152 | case R.id.action_net_album: { 153 | Intent intent = new Intent(MainActivity.this, NetImageActivity.class); 154 | startActivity(intent); 155 | break; 156 | } 157 | case R.id.action_time_line: { 158 | Intent intent = new Intent(MainActivity.this, TimeLineActivity.class); 159 | startActivity(intent); 160 | break; 161 | } 162 | case R.id.action_clear_cache: 163 | MyApplication.clearCache(this); 164 | break; 165 | } 166 | return true; 167 | } 168 | 169 | @Override 170 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 171 | switch (seekBar.getId()) { 172 | case R.id.seek_bar_width: { 173 | dragParentView.requestChildWidth(seekBar.getProgress()); 174 | break; 175 | } 176 | case R.id.seek_bar_height: { 177 | dragParentView.requestChildHeight(seekBar.getProgress()); 178 | break; 179 | } 180 | } 181 | } 182 | 183 | @Override 184 | public void onStartTrackingTouch(SeekBar seekBar) { 185 | 186 | } 187 | 188 | @Override 189 | public void onStopTrackingTouch(SeekBar seekBar) { 190 | seekBar.postDelayed(new Runnable() { 191 | @Override 192 | public void run() { 193 | if (imageView != null) 194 | loadImage(); 195 | } 196 | }, 300); 197 | } 198 | 199 | public void clickToScaleType(View view) { 200 | switch (view.getId()) { 201 | case R.id.center_crop: 202 | scaleType = ScaleType.CENTER_CROP; 203 | break; 204 | case R.id.top_crop: 205 | scaleType = ScaleType.START_CROP; 206 | break; 207 | case R.id.bottom_crop: 208 | scaleType = ScaleType.END_CROP; 209 | break; 210 | case R.id.fix_xy: 211 | scaleType = ScaleType.FIT_XY; 212 | break; 213 | } 214 | centerCrop.setSelected(centerCrop.getId() == view.getId()); 215 | startCrop.setSelected(startCrop.getId() == view.getId()); 216 | endCrop.setSelected(endCrop.getId() == view.getId()); 217 | fitXy.setSelected(fitXy.getId() == view.getId()); 218 | loadImage(); 219 | } 220 | 221 | private void loadImage() { 222 | Glide.with(this).load(drawableId) 223 | .transform(new CustomTransform(this, scaleType)) 224 | .into(imageView); 225 | } 226 | 227 | } 228 | -------------------------------------------------------------------------------- /app/src/main/java/com/demo/imagetransdemo/activity/NetImageActivity.java: -------------------------------------------------------------------------------- 1 | package com.demo.imagetransdemo.activity; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.support.v7.widget.GridLayoutManager; 8 | import android.support.v7.widget.RecyclerView; 9 | import android.support.v7.widget.Toolbar; 10 | import android.util.SparseArray; 11 | import android.view.Menu; 12 | import android.view.MenuItem; 13 | import android.view.View; 14 | import android.view.ViewGroup; 15 | import android.widget.ImageView; 16 | 17 | import com.bumptech.glide.Glide; 18 | import com.demo.imagetransdemo.MyApplication; 19 | import com.demo.imagetransdemo.R; 20 | import com.demo.imagetransdemo.adapter.CustomTransform; 21 | import com.demo.imagetransdemo.adapter.MyImageLoad; 22 | import com.demo.imagetransdemo.adapter.MyImageTransAdapter; 23 | import com.demo.imagetransdemo.adapter.MyProgressBarGet; 24 | import com.demo.imagetransdemo.view.RingLoadingView; 25 | import com.demo.imagetransdemo.view.SpaceDecoration; 26 | 27 | import java.util.ArrayList; 28 | import java.util.Arrays; 29 | import java.util.List; 30 | 31 | import it.liuting.imagetrans.ImageTrans; 32 | import it.liuting.imagetrans.ScaleType; 33 | import it.liuting.imagetrans.listener.SourceImageViewGet; 34 | 35 | /** 36 | * Created by liuting on 17/6/1. 37 | */ 38 | 39 | public class NetImageActivity extends AppCompatActivity { 40 | 41 | public static String[] netImages = { 42 | "http://wx1.sinaimg.cn/bmiddle/9672f95cly1fgcl0xc7hmj20gt5fwhdt.jpg", 43 | "https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=4210120443,3922685164&fm=27&gp=0.jpg", 44 | "https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=2966021298,3341101515&fm=23&gp=0.jpg", 45 | "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1496402134202&di=6c7f4a6afa5bdf02000c788f7a51e9c0&imgtype=0&src=http%3A%2F%2Fcdnq.duitang.com%2Fuploads%2Fitem%2F201506%2F23%2F20150623183946_iZtFs.jpeg", 46 | "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1496996892&di=ea1e213c8ddd4427c55f073db9bf91b7&imgtype=jpg&er=1&src=http%3A%2F%2Fpic27.nipic.com%2F20130323%2F9483785_182530048000_2.jpg", 47 | "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1496996959&di=13c094ba73675a24df2ad1d2c730c02c&imgtype=jpg&er=1&src=http%3A%2F%2Fdasouji.com%2Fwp-content%2Fuploads%2F2015%2F07%2F%25E9%2595%25BF%25E8%258A%25B1%25E5%259B%25BE-6.jpg" 48 | }; 49 | 50 | private Toolbar toolbar; 51 | private RecyclerView recyclerView; 52 | 53 | private PhotoAlbumAdapter adapter; 54 | private int itemSize; 55 | 56 | private ScaleType scaleType = ScaleType.CENTER_CROP; 57 | 58 | @Override 59 | protected void onCreate(@Nullable Bundle savedInstanceState) { 60 | super.onCreate(savedInstanceState); 61 | setContentView(R.layout.activity_photo_album); 62 | toolbar = (Toolbar) findViewById(R.id.toolbar); 63 | setSupportActionBar(toolbar); 64 | recyclerView = (RecyclerView) findViewById(R.id.recycle_view); 65 | recyclerView.setLayoutManager(new GridLayoutManager(this, 3)); 66 | recyclerView.addItemDecoration(new SpaceDecoration(10)); 67 | itemSize = (MyApplication.getScreenWidth() - 10 * 4) / 3; 68 | adapter = new PhotoAlbumAdapter(this); 69 | recyclerView.setAdapter(adapter); 70 | adapter.setData(Arrays.asList(netImages)); 71 | adapter.notifyDataSetChanged(); 72 | } 73 | 74 | @Override 75 | public boolean onCreateOptionsMenu(Menu menu) { 76 | getMenuInflater().inflate(R.menu.menu_scale_type, menu); 77 | return super.onCreateOptionsMenu(menu); 78 | } 79 | 80 | @Override 81 | public boolean onOptionsItemSelected(MenuItem item) { 82 | switch (item.getItemId()) { 83 | case R.id.action_center_crop: 84 | scaleType = ScaleType.CENTER_CROP; 85 | break; 86 | case R.id.action_start_crop: 87 | scaleType = ScaleType.START_CROP; 88 | break; 89 | case R.id.action_end_crop: 90 | scaleType = ScaleType.END_CROP; 91 | break; 92 | case R.id.action_fit_xy: 93 | scaleType = ScaleType.FIT_XY; 94 | break; 95 | } 96 | adapter.notifyDataSetChanged(); 97 | return true; 98 | } 99 | 100 | class PhotoAlbumAdapter extends RecyclerView.Adapter { 101 | 102 | private Context context; 103 | private List images = new ArrayList<>(); 104 | 105 | PhotoAlbumAdapter(Context context) { 106 | this.context = context; 107 | } 108 | 109 | public void setData(List images) { 110 | this.images = images; 111 | } 112 | 113 | @Override 114 | public PhotoViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 115 | ImageView imageView = new ImageView(parent.getContext()); 116 | imageView.setLayoutParams(new ViewGroup.LayoutParams(itemSize, itemSize)); 117 | return new PhotoViewHolder(imageView); 118 | } 119 | 120 | @Override 121 | public void onBindViewHolder(final PhotoViewHolder holder, final int position) { 122 | Glide.with(context).load(images.get(position)) 123 | .placeholder(R.drawable.place_holder) 124 | .transform(new CustomTransform(context, scaleType)) 125 | .into((ImageView) holder.itemView); 126 | holder.itemView.setOnClickListener(new View.OnClickListener() { 127 | @Override 128 | public void onClick(View v) { 129 | ImageTrans.with(context) 130 | .setImageList(images) 131 | .setSourceImageView(new SourceImageViewGet() { 132 | @Override 133 | public ImageView getImageView(int pos) { 134 | int layoutPos = recyclerView.indexOfChild(holder.itemView); 135 | View view = recyclerView.getChildAt(layoutPos + pos - position); 136 | if (view != null) return (ImageView) view; 137 | return (ImageView) holder.itemView; 138 | } 139 | }) 140 | .setImageLoad(new MyImageLoad()) 141 | .setNowIndex(position) 142 | .setProgressBar(new MyProgressBarGet()) 143 | .setAdapter(new MyImageTransAdapter()) 144 | .show(); 145 | } 146 | }); 147 | } 148 | 149 | @Override 150 | public int getItemCount() { 151 | return images.size(); 152 | } 153 | } 154 | 155 | class PhotoViewHolder extends RecyclerView.ViewHolder { 156 | 157 | private SparseArray views; 158 | 159 | public PhotoViewHolder(View itemView) { 160 | super(itemView); 161 | } 162 | 163 | public E get(int id) { 164 | View childView = views.get(id); 165 | if (null == childView) { 166 | childView = itemView.findViewById(id); 167 | views.put(id, childView); 168 | } 169 | return (E) childView; 170 | } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /app/src/main/java/com/demo/imagetransdemo/activity/PhotoAlbumActivity.java: -------------------------------------------------------------------------------- 1 | package com.demo.imagetransdemo.activity; 2 | 3 | import android.content.ContentResolver; 4 | import android.content.Context; 5 | import android.database.Cursor; 6 | import android.net.Uri; 7 | import android.os.AsyncTask; 8 | import android.os.Bundle; 9 | import android.provider.MediaStore; 10 | import android.support.annotation.Nullable; 11 | import android.support.v7.app.AppCompatActivity; 12 | import android.support.v7.widget.GridLayoutManager; 13 | import android.support.v7.widget.RecyclerView; 14 | import android.support.v7.widget.Toolbar; 15 | import android.util.SparseArray; 16 | import android.view.Menu; 17 | import android.view.MenuItem; 18 | import android.view.View; 19 | import android.view.ViewGroup; 20 | import android.widget.ImageView; 21 | 22 | import com.bumptech.glide.Glide; 23 | import com.demo.imagetransdemo.MyApplication; 24 | import com.demo.imagetransdemo.R; 25 | import com.demo.imagetransdemo.adapter.CustomTransform; 26 | import com.demo.imagetransdemo.adapter.MyImageLoad; 27 | import com.demo.imagetransdemo.view.SpaceDecoration; 28 | 29 | import java.util.ArrayList; 30 | import java.util.List; 31 | 32 | import it.liuting.imagetrans.ImageTrans; 33 | import it.liuting.imagetrans.ScaleType; 34 | import it.liuting.imagetrans.listener.SourceImageViewGet; 35 | 36 | /** 37 | * Created by liuting on 17/6/1. 38 | */ 39 | 40 | public class PhotoAlbumActivity extends AppCompatActivity { 41 | private Toolbar toolbar; 42 | private RecyclerView recyclerView; 43 | 44 | private PhotoAlbumAdapter adapter; 45 | private int itemSize; 46 | 47 | private ScaleType scaleType = ScaleType.CENTER_CROP; 48 | 49 | @Override 50 | protected void onCreate(@Nullable Bundle savedInstanceState) { 51 | super.onCreate(savedInstanceState); 52 | setContentView(R.layout.activity_photo_album); 53 | toolbar = (Toolbar) findViewById(R.id.toolbar); 54 | setSupportActionBar(toolbar); 55 | recyclerView = (RecyclerView) findViewById(R.id.recycle_view); 56 | recyclerView.setLayoutManager(new GridLayoutManager(this, 3)); 57 | recyclerView.addItemDecoration(new SpaceDecoration(10)); 58 | itemSize = (MyApplication.getScreenWidth() - 10 * 4) / 3; 59 | adapter = new PhotoAlbumAdapter(this); 60 | recyclerView.setAdapter(adapter); 61 | new ReadImagesTask(this).execute(); 62 | } 63 | 64 | @Override 65 | public boolean onCreateOptionsMenu(Menu menu) { 66 | getMenuInflater().inflate(R.menu.menu_scale_type, menu); 67 | return super.onCreateOptionsMenu(menu); 68 | } 69 | 70 | @Override 71 | public boolean onOptionsItemSelected(MenuItem item) { 72 | switch (item.getItemId()) { 73 | case R.id.action_center_crop: 74 | scaleType = ScaleType.CENTER_CROP; 75 | break; 76 | case R.id.action_start_crop: 77 | scaleType = ScaleType.START_CROP; 78 | break; 79 | case R.id.action_end_crop: 80 | scaleType = ScaleType.END_CROP; 81 | break; 82 | case R.id.action_fit_xy: 83 | scaleType = ScaleType.FIT_XY; 84 | break; 85 | } 86 | adapter.notifyDataSetChanged(); 87 | return true; 88 | } 89 | 90 | class PhotoAlbumAdapter extends RecyclerView.Adapter { 91 | 92 | private Context context; 93 | private List images = new ArrayList<>(); 94 | 95 | PhotoAlbumAdapter(Context context) { 96 | this.context = context; 97 | } 98 | 99 | public void setData(List images) { 100 | this.images = images; 101 | } 102 | 103 | @Override 104 | public PhotoViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 105 | ImageView imageView = new ImageView(parent.getContext()); 106 | imageView.setLayoutParams(new ViewGroup.LayoutParams(itemSize, itemSize)); 107 | return new PhotoViewHolder(imageView); 108 | } 109 | 110 | @Override 111 | public void onBindViewHolder(final PhotoViewHolder holder, final int position) { 112 | Glide.with(context).load(images.get(position)) 113 | .placeholder(R.drawable.place_holder) 114 | .transform(new CustomTransform(context, scaleType)) 115 | .into((ImageView) holder.itemView); 116 | holder.itemView.setOnClickListener(new View.OnClickListener() { 117 | @Override 118 | public void onClick(View v) { 119 | ImageTrans.with(context) 120 | .setImageList(images) 121 | .setSourceImageView(new SourceImageViewGet() { 122 | @Override 123 | public ImageView getImageView(int pos) { 124 | int layoutPos = recyclerView.indexOfChild(holder.itemView); 125 | int viewPos = layoutPos + pos - position; 126 | View view = recyclerView.getChildAt(viewPos); 127 | if (view != null) return (ImageView) view; 128 | return null; 129 | } 130 | }) 131 | .setScaleType(scaleType) 132 | .setImageLoad(new MyImageLoad()) 133 | .setNowIndex(position) 134 | .show(); 135 | } 136 | }); 137 | } 138 | 139 | @Override 140 | public int getItemCount() { 141 | return images.size(); 142 | } 143 | } 144 | 145 | class PhotoViewHolder extends RecyclerView.ViewHolder { 146 | 147 | private SparseArray views; 148 | 149 | public PhotoViewHolder(View itemView) { 150 | super(itemView); 151 | } 152 | 153 | public E get(int id) { 154 | View childView = views.get(id); 155 | if (null == childView) { 156 | childView = itemView.findViewById(id); 157 | views.put(id, childView); 158 | } 159 | return (E) childView; 160 | } 161 | } 162 | 163 | private class ReadImagesTask extends AsyncTask> { 164 | private Context context; 165 | 166 | private ReadImagesTask(Context context) { 167 | this.context = context; 168 | } 169 | 170 | @Override 171 | protected List doInBackground(Void[] params) { 172 | Uri mImageUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; 173 | String[] columns = new String[]{ 174 | MediaStore.Images.Media.DATA, 175 | MediaStore.Images.Media.DATE_MODIFIED 176 | }; 177 | String sortOrder = MediaStore.Images.Media.DATE_MODIFIED + " DESC"; 178 | 179 | ContentResolver mContentResolver = context.getContentResolver(); 180 | Cursor mCursor = mContentResolver.query(mImageUri, columns, null, null, sortOrder); 181 | if (mCursor == null) { 182 | return null; 183 | } 184 | 185 | List imagePathList = new ArrayList<>(); 186 | while (mCursor.moveToNext()) { 187 | //获取图片的路径 188 | String path = mCursor.getString(mCursor.getColumnIndex(MediaStore.Images.Media.DATA)); 189 | if (!path.endsWith(".gif") && !path.endsWith(".GIF")) 190 | imagePathList.add("file:///" + path); 191 | } 192 | mCursor.close(); 193 | return imagePathList; 194 | } 195 | 196 | @Override 197 | protected void onPostExecute(List imageUriList) { 198 | adapter.setData(imageUriList); 199 | adapter.notifyDataSetChanged(); 200 | } 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /app/src/main/java/com/demo/imagetransdemo/activity/TimeLineActivity.java: -------------------------------------------------------------------------------- 1 | package com.demo.imagetransdemo.activity; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.support.v7.widget.LinearLayoutManager; 7 | import android.support.v7.widget.RecyclerView; 8 | 9 | import com.demo.imagetransdemo.ItemData; 10 | import com.demo.imagetransdemo.MyApplication; 11 | import com.demo.imagetransdemo.R; 12 | import com.demo.imagetransdemo.adapter.TimeLineAdapter; 13 | 14 | import org.json.JSONArray; 15 | import org.json.JSONException; 16 | import org.json.JSONObject; 17 | 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | 21 | /** 22 | * Created by liuting on 17/6/20. 23 | */ 24 | 25 | public class TimeLineActivity extends AppCompatActivity { 26 | RecyclerView recyclerView; 27 | TimeLineAdapter timeLineAdapter; 28 | 29 | @Override 30 | protected void onCreate(@Nullable Bundle savedInstanceState) { 31 | super.onCreate(savedInstanceState); 32 | setContentView(R.layout.activity_time_line); 33 | recyclerView = (RecyclerView) findViewById(R.id.recycle_view); 34 | recyclerView.setLayoutManager(new LinearLayoutManager(this)); 35 | loadData(); 36 | } 37 | 38 | private void loadData() { 39 | MyApplication.cThreadPool.submit(new Runnable() { 40 | @Override 41 | public void run() { 42 | List datas = new ArrayList<>(); 43 | String response = MyApplication.getFromAssets(TimeLineActivity.this, "data.json"); 44 | try { 45 | JSONArray jsonArray = new JSONArray(response); 46 | for (int i = 0; i < jsonArray.length(); i++) { 47 | ItemData itemData = new ItemData(); 48 | JSONObject jsonObject = jsonArray.optJSONObject(i); 49 | itemData.text = jsonObject.optString("text"); 50 | JSONArray images = jsonObject.optJSONArray("images"); 51 | for (int j = 0; j < images.length(); j++) { 52 | String url = images.optString(j); 53 | itemData.images.add(url); 54 | } 55 | datas.add(itemData); 56 | } 57 | } catch (JSONException e) { 58 | e.printStackTrace(); 59 | } 60 | setData(datas); 61 | } 62 | }); 63 | } 64 | 65 | private void setData(final List datas) { 66 | runOnUiThread(new Runnable() { 67 | @Override 68 | public void run() { 69 | timeLineAdapter = new TimeLineAdapter(datas, TimeLineActivity.this); 70 | recyclerView.setAdapter(timeLineAdapter); 71 | } 72 | }); 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /app/src/main/java/com/demo/imagetransdemo/adapter/CustomTransform.java: -------------------------------------------------------------------------------- 1 | package com.demo.imagetransdemo.adapter; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.Canvas; 6 | import android.graphics.Matrix; 7 | import android.graphics.Paint; 8 | 9 | import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool; 10 | import com.bumptech.glide.load.resource.bitmap.BitmapTransformation; 11 | import com.bumptech.glide.load.resource.bitmap.TransformationUtils; 12 | 13 | import it.liuting.imagetrans.ScaleType; 14 | 15 | /** 16 | * Created by liuting on 17/6/1. 17 | */ 18 | 19 | public class CustomTransform extends BitmapTransformation { 20 | ScaleType scaleType; 21 | 22 | public CustomTransform(Context context, ScaleType scaleType) { 23 | super(context); 24 | this.scaleType = scaleType; 25 | } 26 | 27 | @Override 28 | protected Bitmap transform(BitmapPool pool, Bitmap source, int outWidth, int outHeight) { 29 | if (null == source || source.isRecycled()) { 30 | return null; 31 | } 32 | switch (scaleType) { 33 | case CENTER_CROP: { 34 | final Bitmap toReuse = pool.get(outWidth, outHeight, source.getConfig() != null 35 | ? source.getConfig() : Bitmap.Config.ARGB_8888); 36 | Bitmap transformed = TransformationUtils.centerCrop(toReuse, source, outWidth, outHeight); 37 | if (toReuse != null && toReuse != transformed && !pool.put(toReuse)) { 38 | toReuse.recycle(); 39 | } 40 | return transformed; 41 | } 42 | case START_CROP: { 43 | float scaleX = (float) outWidth / source.getWidth(); 44 | float scaleY = (float) outHeight / source.getHeight(); 45 | float scale = Math.max(scaleX, scaleY); 46 | int finalWidth = (int) (outWidth / scale); 47 | int finalHeight = (int) (outHeight / scale); 48 | if (finalWidth > source.getWidth()) finalWidth = source.getWidth(); 49 | if (finalHeight > source.getHeight()) finalHeight = source.getHeight(); 50 | Bitmap result = Bitmap.createBitmap(source, 0, 0, finalWidth, finalHeight); 51 | return result; 52 | } 53 | case END_CROP: { 54 | float scaleX = (float) outWidth / source.getWidth(); 55 | float scaleY = (float) outHeight / source.getHeight(); 56 | float scale = Math.max(scaleX, scaleY); 57 | int finalWidth = (int) (outWidth / scale); 58 | int finalHeight = (int) (outHeight / scale); 59 | if (finalWidth > source.getWidth()) finalWidth = source.getWidth(); 60 | if (finalHeight > source.getHeight()) finalHeight = source.getHeight(); 61 | Bitmap result = Bitmap.createBitmap(source, source.getWidth() - finalWidth, source.getHeight() - finalHeight, finalWidth, finalHeight); 62 | return result; 63 | } 64 | case FIT_XY: { 65 | float scaleX = (float) outWidth / source.getWidth(); 66 | float scaleY = (float) outHeight / source.getHeight(); 67 | Bitmap result = Bitmap.createBitmap(outWidth, outHeight, getSafeConfig(source)); 68 | Matrix matrix = new Matrix(); 69 | matrix.setScale(scaleX, scaleY); 70 | final Canvas canvas = new Canvas(result); 71 | final Paint paint = new Paint(Paint.DITHER_FLAG | Paint.FILTER_BITMAP_FLAG); 72 | canvas.drawBitmap(source, matrix, paint); 73 | return result; 74 | } 75 | } 76 | return source; 77 | } 78 | 79 | private static Bitmap.Config getSafeConfig(Bitmap bitmap) { 80 | return bitmap.getConfig() != null ? bitmap.getConfig() : Bitmap.Config.ARGB_8888; 81 | } 82 | 83 | @Override 84 | public String getId() { 85 | return scaleType.name(); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /app/src/main/java/com/demo/imagetransdemo/adapter/MyImageLoad.java: -------------------------------------------------------------------------------- 1 | package com.demo.imagetransdemo.adapter; 2 | 3 | import android.content.ContentResolver; 4 | import android.graphics.drawable.Drawable; 5 | import android.net.Uri; 6 | import android.util.Log; 7 | import android.widget.ImageView; 8 | 9 | import com.demo.imagetransdemo.MyApplication; 10 | import com.demo.imagetransdemo.TileBitmapDrawable; 11 | import com.demo.imagetransdemo.imageload.OkHttpImageLoad; 12 | 13 | import java.util.HashMap; 14 | import java.util.regex.Pattern; 15 | 16 | import it.liuting.imagetrans.ImageLoad; 17 | 18 | 19 | /** 20 | * Created by liuting on 17/6/1. 21 | */ 22 | 23 | public class MyImageLoad implements ImageLoad { 24 | private static final Pattern webPattern = Pattern.compile("http[s]*://[[[^/:]&&[a-zA-Z_0-9]]\\.]+(:\\d+)?(/[a-zA-Z_0-9]+)*(/[a-zA-Z_0-9]*([a-zA-Z_0-9]+\\.[a-zA-Z_0-9]+)*)?(\\?(&?[a-zA-Z_0-9]+=[%[a-zA-Z_0-9]-]*)*)*(#[[a-zA-Z_0-9]|-]+)?(.jpg|.png|.gif|.jpeg)?"); 25 | private static final String ASSET_PATH_SEGMENT = "android_asset"; 26 | private static final HashMap loadCallbackMap = new HashMap<>(); 27 | private static final HashMap imageDownLoadListenerMap = new HashMap<>(); 28 | 29 | @Override 30 | public void loadImage(final String url, final LoadCallback callback, final ImageView imageView, final String unique) { 31 | addLoadCallback(unique, callback); 32 | Uri uri = Uri.parse(url); 33 | if (isLocalUri(uri.getScheme())) { 34 | if (isAssetUri(uri)) { 35 | //是asset资源文件 36 | 37 | return; 38 | } else { 39 | //是本地文件 40 | loadImageFromLocal(uri.getPath(), unique, imageView); 41 | return; 42 | } 43 | } else { 44 | if (isNetUri(url)) { 45 | loadImageFromNet(url, unique, imageView); 46 | return; 47 | } 48 | Log.e("MyImageLoad", "未知的图片URL的类型"); 49 | } 50 | } 51 | 52 | /** 53 | * 从网络加载图片 54 | */ 55 | private void loadImageFromNet(final String url, final String unique, final ImageView imageView) { 56 | OkHttpImageLoad.ImageDownLoadListener loadListener = new OkHttpImageLoad.ImageDownLoadListener() { 57 | @Override 58 | public void inProgress(float progress, long total) { 59 | onProgress(unique, progress); 60 | } 61 | 62 | @Override 63 | public void onError(Exception e) { 64 | 65 | } 66 | 67 | @Override 68 | public void onSuccess() { 69 | loadImageFromLocal(MyApplication.getCachedPath(url), unique, imageView); 70 | } 71 | 72 | @Override 73 | public void onCancel() { 74 | 75 | } 76 | 77 | }; 78 | imageDownLoadListenerMap.put(unique, loadListener); 79 | OkHttpImageLoad.load(url, loadListener); 80 | } 81 | 82 | /** 83 | * 从本地加载图片 84 | */ 85 | private void loadImageFromLocal(String url, final String unique, final ImageView imageView) { 86 | TileBitmapDrawable.attachTileBitmapDrawable(imageView, url, new TileBitmapDrawable.OnLoadListener() { 87 | @Override 88 | public void onLoadFinish(TileBitmapDrawable drawable) { 89 | onFinishLoad(unique, drawable); 90 | } 91 | 92 | @Override 93 | public void onError(Exception ex) { 94 | 95 | } 96 | }); 97 | } 98 | 99 | public static void addLoadCallback(String unique, LoadCallback callback) { 100 | loadCallbackMap.put(unique, callback); 101 | } 102 | 103 | public static void removeLoadCallback(String unique) { 104 | loadCallbackMap.remove(unique); 105 | } 106 | 107 | public static void onFinishLoad(String unique, Drawable drawable) { 108 | LoadCallback loadCallback = loadCallbackMap.remove(unique); 109 | if (loadCallback != null) { 110 | loadCallback.loadFinish(drawable); 111 | } 112 | } 113 | 114 | public static void onProgress(String unique, float progress) { 115 | LoadCallback loadCallback = loadCallbackMap.get(unique); 116 | if (loadCallback != null) { 117 | loadCallback.progress(progress); 118 | } 119 | } 120 | 121 | 122 | @Override 123 | public boolean isCached(String url) { 124 | if (isLocalUri(Uri.parse(url).getScheme())) { 125 | //是本地图片不用预览图 126 | return true; 127 | } 128 | return OkHttpImageLoad.checkImageExists(url); 129 | } 130 | 131 | @Override 132 | public void cancel(String url, String unique) { 133 | removeLoadCallback(unique); 134 | 135 | OkHttpImageLoad.destroy(url, imageDownLoadListenerMap.remove(unique)); 136 | } 137 | 138 | private static boolean isNetUri(String url) { 139 | return webPattern.matcher(url).find(); 140 | } 141 | 142 | private static boolean isLocalUri(String scheme) { 143 | return ContentResolver.SCHEME_FILE.equals(scheme) 144 | || ContentResolver.SCHEME_CONTENT.equals(scheme) 145 | || ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme); 146 | } 147 | 148 | public static boolean isAssetUri(Uri uri) { 149 | return ContentResolver.SCHEME_FILE.equals(uri.getScheme()) && !uri.getPathSegments().isEmpty() 150 | && ASSET_PATH_SEGMENT.equals(uri.getPathSegments().get(0)); 151 | } 152 | 153 | } 154 | -------------------------------------------------------------------------------- /app/src/main/java/com/demo/imagetransdemo/adapter/MyImageTransAdapter.java: -------------------------------------------------------------------------------- 1 | package com.demo.imagetransdemo.adapter; 2 | 3 | import android.content.DialogInterface; 4 | import android.support.v4.view.ViewPager; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.widget.Toast; 8 | 9 | import com.demo.imagetransdemo.MyApplication; 10 | import com.demo.imagetransdemo.R; 11 | import com.demo.imagetransdemo.TileBitmapDrawable; 12 | import com.demo.imagetransdemo.view.RoundPageIndicator; 13 | 14 | import it.liuting.imagetrans.ImageTransAdapter; 15 | 16 | 17 | /** 18 | * Created by liuting on 17/6/15. 19 | */ 20 | 21 | public class MyImageTransAdapter extends ImageTransAdapter { 22 | private View view; 23 | private View topPanel; 24 | private RoundPageIndicator bottomPanel; 25 | private boolean isShow = true; 26 | 27 | @Override 28 | protected View onCreateView(View parent, ViewPager viewPager, final DialogInterface dialogInterface) { 29 | view = LayoutInflater.from(parent.getContext()).inflate(R.layout.image_trans_adapter, null); 30 | topPanel = view.findViewById(R.id.top_panel); 31 | bottomPanel = (RoundPageIndicator) view.findViewById(R.id.page_indicator); 32 | view.findViewById(R.id.top_panel_cancel).setOnClickListener(new View.OnClickListener() { 33 | @Override 34 | public void onClick(View v) { 35 | dialogInterface.cancel(); 36 | } 37 | }); 38 | topPanel.setTranslationY(-MyApplication.dpToPx(56)); 39 | bottomPanel.setTranslationY(MyApplication.dpToPx(80)); 40 | bottomPanel.setViewPager(viewPager); 41 | return view; 42 | } 43 | 44 | @Override 45 | public void onPullRange(float range) { 46 | topPanel.setTranslationY(-MyApplication.dpToPx(56) * range * 4); 47 | bottomPanel.setTranslationY(MyApplication.dpToPx(80) * range * 4); 48 | } 49 | 50 | @Override 51 | public void onPullCancel() { 52 | showPanel(); 53 | } 54 | 55 | @Override 56 | protected void onOpenTransStart() { 57 | showPanel(); 58 | } 59 | 60 | @Override 61 | protected void onOpenTransEnd() { 62 | 63 | } 64 | 65 | @Override 66 | protected void onCloseTransStart() { 67 | hiddenPanel(); 68 | } 69 | 70 | @Override 71 | protected void onCloseTransEnd() { 72 | TileBitmapDrawable.clearCache(); 73 | } 74 | 75 | @Override 76 | protected boolean onClick(View v,int pos) { 77 | if (isShow) { 78 | showPanel(); 79 | } else { 80 | hiddenPanel(); 81 | } 82 | isShow = !isShow; 83 | 84 | return true; 85 | } 86 | 87 | @Override 88 | protected void onLongClick(View v,int pos) { 89 | Toast.makeText(view.getContext(), "long click", Toast.LENGTH_SHORT).show(); 90 | } 91 | 92 | public void hiddenPanel() { 93 | topPanel.animate().translationY(-MyApplication.dpToPx(56)).setDuration(200).start(); 94 | bottomPanel.animate().translationY(MyApplication.dpToPx(80)).setDuration(200).start(); 95 | } 96 | 97 | public void showPanel() { 98 | topPanel.animate().translationY(0).setDuration(200).start(); 99 | bottomPanel.animate().translationY(0).setDuration(200).start(); 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /app/src/main/java/com/demo/imagetransdemo/adapter/MyProgressBarGet.java: -------------------------------------------------------------------------------- 1 | package com.demo.imagetransdemo.adapter; 2 | 3 | import android.content.Context; 4 | import android.view.ViewGroup; 5 | 6 | import com.demo.imagetransdemo.MyApplication; 7 | import com.demo.imagetransdemo.view.RingLoadingView; 8 | 9 | import it.liuting.imagetrans.listener.ProgressViewGet; 10 | 11 | /** 12 | * Created by liuting on 18/3/19. 13 | */ 14 | 15 | public class MyProgressBarGet implements ProgressViewGet { 16 | @Override 17 | public RingLoadingView getProgress(Context context) { 18 | RingLoadingView view = new RingLoadingView(context); 19 | view.setLayoutParams(new ViewGroup.LayoutParams(MyApplication.dpToPx(50), MyApplication.dpToPx(50))); 20 | return view; 21 | } 22 | 23 | @Override 24 | public void onProgressChange(RingLoadingView view, float progress) { 25 | view.setProgress(progress); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/demo/imagetransdemo/adapter/TimeLineAdapter.java: -------------------------------------------------------------------------------- 1 | package com.demo.imagetransdemo.adapter; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.GridLayoutManager; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.ImageView; 10 | import android.widget.TextView; 11 | 12 | import com.bumptech.glide.Glide; 13 | import com.demo.imagetransdemo.ItemData; 14 | import com.demo.imagetransdemo.MyApplication; 15 | import com.demo.imagetransdemo.R; 16 | import com.demo.imagetransdemo.view.ItemDecorationAlbumColumns; 17 | import com.demo.imagetransdemo.view.RingLoadingView; 18 | 19 | import java.lang.reflect.Field; 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | 23 | import it.liuting.imagetrans.ITConfig; 24 | import it.liuting.imagetrans.ImageTrans; 25 | import it.liuting.imagetrans.ScaleType; 26 | import it.liuting.imagetrans.listener.SourceImageViewGet; 27 | 28 | /** 29 | * Created by liuting on 17/6/20. 30 | */ 31 | 32 | public class TimeLineAdapter extends RecyclerView.Adapter { 33 | 34 | private List mDatas; 35 | private Context context; 36 | 37 | public TimeLineAdapter(List datas, Context context) { 38 | mDatas = datas; 39 | this.context = context; 40 | } 41 | 42 | @Override 43 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 44 | LayoutInflater inflater = LayoutInflater.from(parent.getContext()); 45 | View view; 46 | switch (viewType) { 47 | case ItemData.VIEW_TYPE_SINGLE: 48 | view = inflater.inflate(R.layout.item_single_image, parent, false); 49 | break; 50 | case ItemData.VIEW_TYPE_MUTIL: 51 | view = inflater.inflate(R.layout.item_single_images, parent, false); 52 | break; 53 | default: 54 | view = inflater.inflate(R.layout.item_single_image, parent, false); 55 | break; 56 | } 57 | return new ViewHolder(view); 58 | } 59 | 60 | @Override 61 | public void onBindViewHolder(ViewHolder holder, int position) { 62 | final ItemData data = mDatas.get(position); 63 | TextView content = holder.get(R.id.item_content); 64 | content.setText(data.text); 65 | int imageParentWidth = (MyApplication.getScreenWidth() - MyApplication.dpToPx(90)); 66 | switch (getItemViewType(position)) { 67 | case ItemData.VIEW_TYPE_SINGLE: 68 | buildSingleImage(data, holder, imageParentWidth); 69 | break; 70 | case ItemData.VIEW_TYPE_MUTIL: 71 | buildMutilImages(data, holder, imageParentWidth); 72 | break; 73 | } 74 | } 75 | 76 | private void buildMutilImages(final ItemData data, ViewHolder holder, int imageParentWidth) { 77 | final RecyclerView recyclerView = holder.get(R.id.item_image); 78 | int column = data.images.size() == 4 ? 2 : 3; 79 | recyclerView.setLayoutManager(new GridLayoutManager(context, column)); 80 | int space = MyApplication.dpToPx(3); 81 | ItemDecorationAlbumColumns itemDecoration = new ItemDecorationAlbumColumns(space, column); 82 | final int itemWidth = (imageParentWidth - space * (column - 1)) / column; 83 | final int itemHeight = column == 2 ? (int) (itemWidth * 9f / 16) : itemWidth; 84 | clearItemDecorations(recyclerView); 85 | recyclerView.addItemDecoration(itemDecoration); 86 | recyclerView.setFocusable(false); 87 | recyclerView.setAdapter(new RecyclerView.Adapter() { 88 | @Override 89 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 90 | ImageView image = new ImageView(parent.getContext()); 91 | ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(itemWidth, itemHeight); 92 | image.setLayoutParams(layoutParams); 93 | return new ViewHolder(image); 94 | } 95 | 96 | @Override 97 | public void onBindViewHolder(final ViewHolder holder, final int position) { 98 | ImageView image = (ImageView) holder.itemView; 99 | Glide.with(context) 100 | .load(data.images.get(position)) 101 | .placeholder(R.drawable.place_holder) 102 | .transform(new CustomTransform(context, ScaleType.CENTER_CROP)) 103 | .into(image); 104 | image.setOnClickListener(new View.OnClickListener() { 105 | @Override 106 | public void onClick(View v) { 107 | ImageTrans.with(context) 108 | .setImageList(data.images) 109 | .setSourceImageView(new SourceImageViewGet() { 110 | @Override 111 | public ImageView getImageView(int pos) { 112 | int layoutPos = recyclerView.indexOfChild(holder.itemView); 113 | View view = recyclerView.getChildAt(layoutPos + pos - position); 114 | if (view != null) return (ImageView) view; 115 | return (ImageView) holder.itemView; 116 | } 117 | }) 118 | .setImageLoad(new MyImageLoad()) 119 | .setNowIndex(position) 120 | .setProgressBar(new MyProgressBarGet()) 121 | .setAdapter(new MyImageTransAdapter()) 122 | .setConfig(new ITConfig().noThumbWhenCached()) 123 | .show(); 124 | } 125 | }); 126 | } 127 | 128 | @Override 129 | public int getItemCount() { 130 | return data.images.size(); 131 | } 132 | }); 133 | } 134 | 135 | static void clearItemDecorations(RecyclerView recyclerView) { 136 | try { 137 | Field field = RecyclerView.class.getDeclaredField("mItemDecorations"); 138 | field.setAccessible(true); 139 | ArrayList itemDecorations = (ArrayList) field.get(recyclerView); 140 | itemDecorations.clear(); 141 | } catch (Throwable e) { 142 | e.printStackTrace(); 143 | } 144 | } 145 | 146 | private void buildSingleImage(final ItemData data, ViewHolder holder, int imageParentWidth) { 147 | final ImageView imageView = holder.get(R.id.item_image); 148 | imageView.getLayoutParams().height = (int) (imageParentWidth * 9f / 16); 149 | imageView.setLayoutParams(imageView.getLayoutParams()); 150 | Glide.with(context) 151 | .load(data.images.get(0)) 152 | .placeholder(R.drawable.place_holder) 153 | .transform(new CustomTransform(context, ScaleType.CENTER_CROP)) 154 | .into(imageView); 155 | imageView.setOnClickListener(new View.OnClickListener() { 156 | @Override 157 | public void onClick(View v) { 158 | ImageTrans.with(context) 159 | .setImageList(data.images) 160 | .setSourceImageView(new SourceImageViewGet() { 161 | @Override 162 | public ImageView getImageView(int pos) { 163 | return imageView; 164 | } 165 | }) 166 | .setImageLoad(new MyImageLoad()) 167 | .setNowIndex(0) 168 | .setProgressBar(new MyProgressBarGet()) 169 | .setAdapter(new MyImageTransAdapter()) 170 | .setConfig(new ITConfig().noThumbWhenCached()) 171 | .show(); 172 | } 173 | }); 174 | } 175 | 176 | @Override 177 | public int getItemViewType(int position) { 178 | return mDatas.get(position).getViewType(); 179 | } 180 | 181 | @Override 182 | public int getItemCount() { 183 | return mDatas.size(); 184 | } 185 | 186 | public class ViewHolder extends RecyclerView.ViewHolder { 187 | 188 | public ViewHolder(View itemView) { 189 | super(itemView); 190 | } 191 | 192 | T get(int id) { 193 | return (T) itemView.findViewById(id); 194 | } 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /app/src/main/java/com/demo/imagetransdemo/imageload/OkHttpImageLoad.java: -------------------------------------------------------------------------------- 1 | package com.demo.imagetransdemo.imageload; 2 | 3 | import android.text.TextUtils; 4 | 5 | import com.demo.imagetransdemo.BuildConfig; 6 | import com.demo.imagetransdemo.MyApplication; 7 | import com.squareup.okhttp.Call; 8 | import com.squareup.okhttp.Callback; 9 | import com.squareup.okhttp.OkHttpClient; 10 | import com.squareup.okhttp.Request; 11 | import com.squareup.okhttp.Response; 12 | 13 | import java.io.File; 14 | import java.io.FileOutputStream; 15 | import java.io.IOException; 16 | import java.io.InputStream; 17 | import java.util.ArrayList; 18 | import java.util.HashMap; 19 | import java.util.LinkedHashMap; 20 | import java.util.List; 21 | import java.util.concurrent.Executor; 22 | 23 | 24 | /** 25 | * Created by liuting on 16/8/26. 26 | */ 27 | public class OkHttpImageLoad { 28 | 29 | private Platform mPlatform; 30 | 31 | private OkHttpClient mOkHttpClient; 32 | 33 | private volatile static OkHttpImageLoad mInstance; 34 | private HashMap map = new LinkedHashMap<>(); 35 | 36 | private OkHttpImageLoad() { 37 | mOkHttpClient = new OkHttpClient(); 38 | mPlatform = Platform.get(); 39 | } 40 | 41 | private Executor getDelivery() { 42 | return mPlatform.defaultCallbackExecutor(); 43 | } 44 | 45 | /** 46 | * 加载图片 47 | * 48 | * @param url 49 | * @param listener 50 | */ 51 | public static void load(String url, ImageDownLoadListener listener) { 52 | if (TextUtils.isEmpty(url)) { 53 | listener.onError(new Exception("链接不能为null")); 54 | return; 55 | } 56 | if (mInstance == null) { 57 | mInstance = new OkHttpImageLoad(); 58 | } 59 | Builder builder = null; 60 | if (mInstance.map.containsKey(url)) { 61 | builder = mInstance.map.get(url); 62 | } else if (checkImageExists(url)) { 63 | //没有发现正在下载,检验是否已经下载过了 64 | listener.onSuccess(); 65 | return; 66 | } 67 | if (builder == null) { 68 | builder = new Builder(url); 69 | mInstance.map.put(url, builder); 70 | } 71 | builder.listener(listener); 72 | builder.start(); 73 | } 74 | 75 | /** 76 | * 判断图片是否已经存在 77 | * 78 | * @param url 79 | * @return 80 | */ 81 | public static boolean checkImageExists(String url) { 82 | String key = MyApplication.generate(url); 83 | String destUrl = MyApplication.getImageCachePath() + "/" + key; 84 | File file = new File(destUrl); 85 | if (file.exists()) { 86 | int size = MyApplication.getMaxSizeOfBitMap(destUrl); 87 | if (size > 0) { 88 | return true; 89 | } else { 90 | file.delete(); 91 | return false; 92 | } 93 | } 94 | return false; 95 | } 96 | 97 | /** 98 | * 解绑监听器,实际下载还在后台进行 99 | * 100 | * @param url 101 | * @param listener 102 | */ 103 | public static void cancel(String url, ImageDownLoadListener listener) { 104 | if (mInstance == null) { 105 | return; 106 | } 107 | if (mInstance.map.containsKey(url)) { 108 | Builder builder = mInstance.map.get(url); 109 | if (builder != null) { 110 | builder.removeListener(listener); 111 | } 112 | } 113 | } 114 | 115 | /** 116 | * 取消下载图片 117 | * 118 | * @param url 119 | * @param listener 120 | */ 121 | public static void destroy(String url, ImageDownLoadListener listener) { 122 | if (mInstance == null) { 123 | return; 124 | } 125 | if (mInstance.map.containsKey(url)) { 126 | Builder builder = mInstance.map.get(url); 127 | if (builder != null) { 128 | mInstance.map.remove(url); 129 | builder.cancel(); 130 | builder.removeListener(listener); 131 | } 132 | } 133 | } 134 | 135 | public static class Builder { 136 | protected Request.Builder builder = new Request.Builder(); 137 | protected String url; 138 | private Request request; 139 | private Call call; 140 | private List imageDownLoadListener = new ArrayList<>(); 141 | private boolean isSuccess = false; 142 | private boolean isStarted = false; 143 | private float currentProgress = 0f; 144 | private long total = 0L; 145 | private State currentState = State.DOWNLOADING; 146 | 147 | private enum State { 148 | DOWNLOADING, DOWNLOADERROR, DOWNLOADFINISH 149 | } 150 | 151 | public Builder(String url) { 152 | this.url = url; 153 | request = builder.url(url).get().build(); 154 | call = mInstance.mOkHttpClient.newCall(request); 155 | } 156 | 157 | public Builder listener(ImageDownLoadListener listener) { 158 | if (!imageDownLoadListener.contains(listener)) 159 | imageDownLoadListener.add(listener); 160 | return this; 161 | } 162 | 163 | public void cancel() { 164 | if (null == call) { 165 | throw new NullPointerException(" cancel() must be called before calling build() "); 166 | } 167 | if (!isSuccess) { 168 | //切换到非UI线程,进行网络的取消工作 169 | MyApplication.cThreadPool.submit(new Runnable() { 170 | @Override 171 | public void run() { 172 | call.cancel(); 173 | } 174 | }); 175 | downloadCancel(); 176 | } 177 | } 178 | 179 | private void execute() { 180 | isStarted = true; 181 | currentState = State.DOWNLOADING; 182 | call.enqueue(new Callback() { 183 | @Override 184 | public void onFailure(Request request, final IOException e) { 185 | downloadFail(e); 186 | } 187 | 188 | @Override 189 | public void onResponse(Response response) throws IOException { 190 | try { 191 | if (call.isCanceled()) { 192 | downloadFail(new Exception("Canceled!")); 193 | return; 194 | } 195 | if (!response.isSuccessful()) { 196 | downloadFail(new Exception("request failed , reponse's code is : " + response.code())); 197 | return; 198 | } 199 | saveFile(response); 200 | downloadSuccess(); 201 | } catch (Exception e) { 202 | downloadFail(e); 203 | } finally { 204 | if (response.body() != null) 205 | response.body().close(); 206 | } 207 | 208 | } 209 | }); 210 | } 211 | 212 | private void saveFile(Response response) throws IOException { 213 | InputStream is = null; 214 | byte[] buf = new byte[2048]; 215 | int len; 216 | FileOutputStream fos = null; 217 | try { 218 | is = response.body().byteStream(); 219 | final long total = response.body().contentLength(); 220 | 221 | long sum = 0; 222 | 223 | File dir = new File(MyApplication.getImageCachePath()); 224 | if (!dir.exists()) { 225 | dir.mkdirs(); 226 | } 227 | String key = MyApplication.generate(url); 228 | String destUrl = MyApplication.getImageCachePath() + "/" + key; 229 | File file = new File(destUrl); 230 | fos = new FileOutputStream(file); 231 | while ((len = is.read(buf)) != -1) { 232 | sum += len; 233 | fos.write(buf, 0, len); 234 | final long finalSum = sum; 235 | refreshProgress(finalSum * 1.0f / total, total); 236 | } 237 | fos.flush(); 238 | } finally { 239 | response.body().close(); 240 | if (is != null) is.close(); 241 | if (fos != null) fos.close(); 242 | } 243 | } 244 | 245 | /** 246 | * 如果已经开启就不再执行网络加载操作 247 | */ 248 | public void start() { 249 | checkState(); 250 | if (!isStarted) { 251 | execute(); 252 | } 253 | } 254 | 255 | private void checkState() { 256 | switch (currentState) { 257 | case DOWNLOADING: 258 | refreshProgress(currentProgress, total); 259 | break; 260 | case DOWNLOADFINISH: 261 | downloadSuccess(); 262 | } 263 | } 264 | 265 | private void downloadCancel() { 266 | for (ImageDownLoadListener listener : imageDownLoadListener) 267 | listener.onCancel(); 268 | } 269 | 270 | private void refreshProgress(final float progress, final long total) { 271 | this.currentProgress = progress; 272 | this.total = total; 273 | mInstance.getDelivery().execute(new Runnable() { 274 | @Override 275 | public void run() { 276 | for (ImageDownLoadListener listener : imageDownLoadListener) 277 | listener.inProgress(progress, total); 278 | } 279 | }); 280 | } 281 | 282 | private void downloadFail(final Exception e) { 283 | currentState = State.DOWNLOADERROR; 284 | String key = MyApplication.generate(url); 285 | String destUrl = MyApplication.getImageCachePath() + "/" + key; 286 | File file = new File(destUrl); 287 | if (file.exists()) file.delete(); 288 | if (imageDownLoadListener.size() == 0) { 289 | //发现没有绑定任何监听,自动移除当前build 290 | mInstance.map.remove(url); 291 | return; 292 | } 293 | mInstance.mPlatform.execute(new Runnable() { 294 | @Override 295 | public void run() { 296 | for (ImageDownLoadListener listener : imageDownLoadListener) 297 | listener.onError(e); 298 | } 299 | }); 300 | } 301 | 302 | private void downloadSuccess() { 303 | isSuccess = true; 304 | currentState = State.DOWNLOADFINISH; 305 | if (imageDownLoadListener.size() == 0) { 306 | //发现没有绑定任何监听,自动移除当前build 307 | mInstance.map.remove(url); 308 | return; 309 | } 310 | mInstance.mPlatform.execute(new Runnable() { 311 | @Override 312 | public void run() { 313 | for (ImageDownLoadListener listener : imageDownLoadListener) 314 | listener.onSuccess(); 315 | } 316 | }); 317 | } 318 | 319 | public void removeListener(ImageDownLoadListener listener) { 320 | imageDownLoadListener.remove(listener); 321 | if (imageDownLoadListener.size() == 0 && currentState == State.DOWNLOADFINISH) { 322 | mInstance.map.remove(url); 323 | } 324 | } 325 | } 326 | 327 | 328 | public interface ImageDownLoadListener { 329 | void inProgress(float progress, long total); 330 | 331 | void onError(Exception e); 332 | 333 | void onSuccess(); 334 | 335 | void onCancel(); 336 | } 337 | 338 | } -------------------------------------------------------------------------------- /app/src/main/java/com/demo/imagetransdemo/imageload/Platform.java: -------------------------------------------------------------------------------- 1 | package com.demo.imagetransdemo.imageload; 2 | 3 | import android.os.Build; 4 | import android.os.Handler; 5 | import android.os.Looper; 6 | import android.util.Log; 7 | 8 | import java.util.concurrent.Executor; 9 | import java.util.concurrent.Executors; 10 | 11 | /** 12 | * Created by liuting on 16/8/26. 13 | */ 14 | public class Platform { 15 | private static final Platform PLATFORM = findPlatform(); 16 | 17 | public static Platform get() { 18 | Log.e("Platform",PLATFORM.getClass().toString()); 19 | return PLATFORM; 20 | } 21 | 22 | private static Platform findPlatform() { 23 | try { 24 | Class.forName("android.os.Build"); 25 | if (Build.VERSION.SDK_INT != 0) { 26 | return new Android(); 27 | } 28 | } catch (ClassNotFoundException ignored) { 29 | } 30 | return new Platform(); 31 | } 32 | 33 | public Executor defaultCallbackExecutor() { 34 | return Executors.newCachedThreadPool(); 35 | } 36 | 37 | public void execute(Runnable runnable) { 38 | defaultCallbackExecutor().execute(runnable); 39 | } 40 | 41 | 42 | static class Android extends Platform { 43 | 44 | @Override 45 | public Executor defaultCallbackExecutor() { 46 | return new MainThreadExecutor(); 47 | } 48 | 49 | static class MainThreadExecutor implements Executor { 50 | private final Handler handler = new Handler(Looper.getMainLooper()); 51 | 52 | @Override 53 | public void execute(Runnable r) { 54 | handler.post(r); 55 | } 56 | } 57 | } 58 | 59 | 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/com/demo/imagetransdemo/view/DragParentView.java: -------------------------------------------------------------------------------- 1 | package com.demo.imagetransdemo.view; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import android.graphics.RectF; 8 | import android.support.annotation.Nullable; 9 | import android.util.AttributeSet; 10 | import android.view.GestureDetector; 11 | import android.view.MotionEvent; 12 | import android.view.View; 13 | import android.view.ViewConfiguration; 14 | import android.view.ViewGroup; 15 | 16 | /** 17 | * Created by liuting on 17/5/27. 18 | */ 19 | 20 | public class DragParentView extends ViewGroup { 21 | View childView; 22 | GestureDetector gestureDetector; 23 | private float lastDownX; 24 | private float lastDownY; 25 | private float mTouchSlop; 26 | private Paint textPaint; 27 | private String drawText = "在此区域拖拽"; 28 | 29 | public DragParentView(Context context) { 30 | this(context, null); 31 | } 32 | 33 | public DragParentView(Context context, @Nullable AttributeSet attrs) { 34 | this(context, attrs, 0); 35 | } 36 | 37 | public DragParentView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 38 | super(context, attrs, defStyleAttr); 39 | init(); 40 | } 41 | 42 | private void init() { 43 | setBackgroundColor(Color.parseColor("#43434343")); 44 | mTouchSlop = ViewConfiguration 45 | .get(getContext()).getScaledTouchSlop(); 46 | gestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() { 47 | @Override 48 | public boolean onSingleTapConfirmed(MotionEvent e) { 49 | float downX = e.getX(); 50 | float downY = e.getY(); 51 | RectF rect = getChildViewRect(); 52 | if (rect.contains(downX, downY)) { 53 | childView.performClick(); 54 | } 55 | return false; 56 | } 57 | }); 58 | textPaint = new Paint(); 59 | textPaint.setColor(Color.WHITE); 60 | textPaint.setStyle(Paint.Style.FILL); 61 | textPaint.setTextSize(60); 62 | } 63 | 64 | public void setView(View view, int width, int height) { 65 | removeAllViews(); 66 | MarginLayoutParams lp = new MarginLayoutParams(width, height); 67 | addView(view, lp); 68 | } 69 | 70 | public void requestChildWidth(int width) { 71 | if (childView == null) return; 72 | childView.getLayoutParams().width = width; 73 | childView.setLayoutParams(childView.getLayoutParams()); 74 | } 75 | 76 | @Override 77 | protected void onDraw(Canvas canvas) { 78 | super.onDraw(canvas); 79 | float textWidth = textPaint.measureText(drawText); 80 | canvas.drawText(drawText, getWidth() / 2 - textWidth / 2, getHeight() / 2, textPaint); 81 | } 82 | 83 | public void requestChildHeight(int height) { 84 | if (childView == null) return; 85 | childView.getLayoutParams().height = height; 86 | childView.setLayoutParams(childView.getLayoutParams()); 87 | } 88 | 89 | @Override 90 | protected void onLayout(boolean changed, int l, int t, int r, int b) { 91 | if (getChildCount() == 0) return; 92 | childView = getChildAt(0); 93 | MarginLayoutParams lp = (MarginLayoutParams) childView.getLayoutParams(); 94 | int left = lp.leftMargin; 95 | int top = lp.topMargin; 96 | if (left < 0) left = 0; 97 | if (top < 0) top = 0; 98 | if (left + lp.width > getWidth()) 99 | left = getWidth() - lp.width; 100 | if (top + lp.height > getHeight()) 101 | top = getHeight() - lp.height; 102 | 103 | int right = left + lp.width; 104 | int bottom = top + lp.height; 105 | childView.layout(left, top, right, bottom); 106 | } 107 | 108 | boolean isDragFlag = false; 109 | 110 | @Override 111 | public boolean onInterceptTouchEvent(MotionEvent ev) { 112 | return true; 113 | } 114 | 115 | float dx; 116 | float dy; 117 | 118 | @Override 119 | public boolean onTouchEvent(MotionEvent event) { 120 | switch (event.getAction()) { 121 | case MotionEvent.ACTION_DOWN: { 122 | lastDownX = event.getX(); 123 | lastDownY = event.getY(); 124 | if (childView != null) { 125 | RectF rect = getChildViewRect(); 126 | if (rect.contains(lastDownX, lastDownY)) { 127 | isDragFlag = true; 128 | dx = lastDownX - rect.left; 129 | dy = lastDownY - rect.top; 130 | } 131 | } 132 | break; 133 | } 134 | case MotionEvent.ACTION_MOVE: { 135 | if (isDragFlag) { 136 | float downX = event.getX(); 137 | float downY = event.getY(); 138 | float tempX = downX - lastDownX; 139 | float tempY = downY - lastDownY; 140 | if (Math.sqrt((tempX * tempX) + (tempY * tempY)) < mTouchSlop) { 141 | break; 142 | } 143 | lastDownX = downX; 144 | lastDownY = downY; 145 | MarginLayoutParams lp = (MarginLayoutParams) childView.getLayoutParams(); 146 | int left = (int) (downX - dx); 147 | int top = (int) (downY - dy); 148 | lp.leftMargin = left; 149 | lp.topMargin = top; 150 | childView.setLayoutParams(lp); 151 | requestLayout(); 152 | } 153 | break; 154 | } 155 | default: 156 | isDragFlag = false; 157 | break; 158 | } 159 | gestureDetector.onTouchEvent(event); 160 | return true; 161 | } 162 | 163 | private RectF getChildViewRect() { 164 | RectF rect = new RectF(childView.getLeft(), childView.getTop(), childView.getRight(), childView.getBottom()); 165 | return rect; 166 | } 167 | 168 | @Override 169 | protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 170 | return p instanceof MarginLayoutParams; 171 | } 172 | 173 | @Override 174 | public LayoutParams generateDefaultLayoutParams() { 175 | return new MarginLayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 176 | } 177 | 178 | @Override 179 | public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { 180 | return new MarginLayoutParams(getContext(), attrs); 181 | } 182 | 183 | @Override 184 | protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 185 | return new MarginLayoutParams(p.width, p.height); 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /app/src/main/java/com/demo/imagetransdemo/view/ItemDecorationAlbumColumns.java: -------------------------------------------------------------------------------- 1 | package com.demo.imagetransdemo.view; 2 | 3 | import android.graphics.Rect; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.View; 6 | 7 | public class ItemDecorationAlbumColumns extends RecyclerView.ItemDecoration { 8 | 9 | private int mSizeGridSpacingPx; 10 | private int mGridSize; 11 | 12 | private boolean mNeedLeftSpacing = false; 13 | 14 | public ItemDecorationAlbumColumns(int gridSpacingPx, int gridSize) { 15 | mSizeGridSpacingPx = gridSpacingPx; 16 | mGridSize = gridSize; 17 | } 18 | 19 | @Override 20 | public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { 21 | int frameWidth = (int) ((parent.getWidth() - (float) mSizeGridSpacingPx * (mGridSize - 1)) / mGridSize); 22 | int padding = parent.getWidth() / mGridSize - frameWidth; 23 | int itemPosition = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewAdapterPosition(); 24 | if (itemPosition < mGridSize) { 25 | outRect.top = 0; 26 | } else { 27 | outRect.top = mSizeGridSpacingPx; 28 | } 29 | if (itemPosition % mGridSize == 0) { 30 | outRect.left = 0; 31 | outRect.right = padding; 32 | mNeedLeftSpacing = true; 33 | } else if ((itemPosition + 1) % mGridSize == 0) { 34 | mNeedLeftSpacing = false; 35 | outRect.right = 0; 36 | outRect.left = padding; 37 | } else if (mNeedLeftSpacing) { 38 | mNeedLeftSpacing = false; 39 | outRect.left = mSizeGridSpacingPx - padding; 40 | if ((itemPosition + 2) % mGridSize == 0) { 41 | outRect.right = mSizeGridSpacingPx - padding; 42 | } else { 43 | outRect.right = mSizeGridSpacingPx / 2; 44 | } 45 | } else if ((itemPosition + 2) % mGridSize == 0) { 46 | mNeedLeftSpacing = false; 47 | outRect.left = mSizeGridSpacingPx / 2; 48 | outRect.right = mSizeGridSpacingPx - padding; 49 | } else { 50 | mNeedLeftSpacing = false; 51 | outRect.left = mSizeGridSpacingPx / 2; 52 | outRect.right = mSizeGridSpacingPx / 2; 53 | } 54 | outRect.bottom = 0; 55 | } 56 | } -------------------------------------------------------------------------------- /app/src/main/java/com/demo/imagetransdemo/view/RingLoadingView.java: -------------------------------------------------------------------------------- 1 | package com.demo.imagetransdemo.view; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import android.graphics.RectF; 8 | import android.util.AttributeSet; 9 | import android.view.View; 10 | 11 | import com.demo.imagetransdemo.MyApplication; 12 | import com.demo.imagetransdemo.R; 13 | 14 | 15 | /** 16 | * Created by qihuan on 16/9/6. 17 | */ 18 | public class RingLoadingView extends View { 19 | private static final float DEFAULT_SIZE = MyApplication.dpToPx(48); 20 | private final Paint mPaint = new Paint(); 21 | private float mStrokeWidth = MyApplication.dpToPx(1); 22 | private RectF mBounds; 23 | private int mColor; 24 | private int mArcColor; 25 | private float mCircleRadius; 26 | private float mpercent; 27 | private RectF mProgressBounds; 28 | private int mBgColor; 29 | 30 | public RingLoadingView(Context context) { 31 | super(context); 32 | init(); 33 | } 34 | 35 | public RingLoadingView(Context context, AttributeSet attrs) { 36 | super(context, attrs); 37 | init(); 38 | } 39 | 40 | public RingLoadingView(Context context, AttributeSet attrs, int defStyleAttr) { 41 | super(context, attrs, defStyleAttr); 42 | init(); 43 | } 44 | 45 | private void init() { 46 | setBackgroundResource(android.R.color.transparent); 47 | //根据默认的宽高,构建view矩阵 48 | mBounds = new RectF(0, 0, DEFAULT_SIZE, DEFAULT_SIZE); 49 | //向内聚,至少是半个线宽,否则会出现图行出界的情况 50 | mBounds.inset(mStrokeWidth, mStrokeWidth); 51 | //进度条范围 52 | mProgressBounds = new RectF(); 53 | mProgressBounds.set(mBounds); 54 | mProgressBounds.inset(MyApplication.dpToPx(6), MyApplication.dpToPx(6)); 55 | 56 | mPaint.setAntiAlias(true); 57 | mPaint.setStrokeWidth(mStrokeWidth); 58 | 59 | mColor = Color.parseColor("#CCFFFFFF"); 60 | mArcColor = mColor; 61 | mBgColor = getContext().getResources().getColor(R.color.pers30_black); 62 | 63 | mCircleRadius = Math.min(mBounds.height(), mBounds.width()) / 2.0f; 64 | } 65 | 66 | @Override 67 | protected void onDraw(Canvas canvas) { 68 | super.onDraw(canvas); 69 | 70 | //背景描边 71 | mPaint.setColor(mArcColor); 72 | mPaint.setStyle(Paint.Style.STROKE); 73 | canvas.drawCircle(mBounds.centerX(),mBounds.centerY(), mCircleRadius, mPaint); 74 | 75 | //画背景圆 76 | mPaint.setStyle(Paint.Style.FILL); 77 | mPaint.setColor(mBgColor); 78 | canvas.drawCircle(mBounds.centerX(), mBounds.centerY(), mCircleRadius, mPaint); 79 | 80 | //画进度 81 | mPaint.setColor(mColor); 82 | canvas.drawArc(mProgressBounds, -90F, 360 * mpercent, true, mPaint); 83 | 84 | } 85 | 86 | /** 87 | * 设置进度 88 | * 89 | * @param percent 90 | */ 91 | public void setProgress(float percent) { 92 | mpercent = (percent >= 1 ? 1 : percent); 93 | postInvalidate(); 94 | } 95 | 96 | private int halfAlphaColor(int colorValue) { 97 | int startA = (colorValue >> 24) & 0xff; 98 | int startR = (colorValue >> 16) & 0xff; 99 | int startG = (colorValue >> 8) & 0xff; 100 | int startB = colorValue & 0xff; 101 | 102 | return ((startA / 2) << 24) 103 | | (startR << 16) 104 | | (startG << 8) 105 | | startB; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /app/src/main/java/com/demo/imagetransdemo/view/RoundPageIndicator.java: -------------------------------------------------------------------------------- 1 | package com.demo.imagetransdemo.view; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorInflater; 5 | import android.annotation.TargetApi; 6 | import android.content.Context; 7 | import android.database.DataSetObserver; 8 | import android.graphics.drawable.Drawable; 9 | import android.os.Build; 10 | import android.support.annotation.AnimatorRes; 11 | import android.support.annotation.DrawableRes; 12 | import android.support.v4.view.ViewPager; 13 | import android.util.AttributeSet; 14 | import android.view.View; 15 | import android.view.animation.Interpolator; 16 | import android.widget.LinearLayout; 17 | 18 | import com.demo.imagetransdemo.R; 19 | 20 | import static android.support.v4.view.ViewPager.OnPageChangeListener; 21 | 22 | public class RoundPageIndicator extends LinearLayout { 23 | 24 | private final static int DEFAULT_INDICATOR_WIDTH = 4; 25 | private ViewPager mViewpager; 26 | private int mIndicatorMargin = -1; 27 | private int mIndicatorWidth = -1; 28 | private int mIndicatorHeight = -1; 29 | private int mAnimatorResId = R.animator.scale_with_alpha; 30 | private int mAnimatorReverseResId = 0; 31 | private Drawable mIndicatorBackgroundRes; 32 | private Drawable mIndicatorUnselectedBackgroundRes; 33 | private Animator mAnimatorOut; 34 | private Animator mAnimatorIn; 35 | private Animator mImmediateAnimatorOut; 36 | private Animator mImmediateAnimatorIn; 37 | 38 | private int mLastPosition = -1; 39 | private final OnPageChangeListener mInternalPageChangeListener = new OnPageChangeListener() { 40 | 41 | @Override 42 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 43 | } 44 | 45 | @Override 46 | public void onPageSelected(int position) { 47 | 48 | if (mViewpager.getAdapter() == null || mViewpager.getAdapter().getCount() <= 0) { 49 | return; 50 | } 51 | 52 | if (mAnimatorIn.isRunning()) { 53 | mAnimatorIn.end(); 54 | mAnimatorIn.cancel(); 55 | } 56 | 57 | if (mAnimatorOut.isRunning()) { 58 | mAnimatorOut.end(); 59 | mAnimatorOut.cancel(); 60 | } 61 | 62 | View currentIndicator; 63 | if (mLastPosition >= 0 && (currentIndicator = getChildAt(mLastPosition)) != null) { 64 | currentIndicator.setBackgroundDrawable(mIndicatorUnselectedBackgroundRes); 65 | mAnimatorIn.setTarget(currentIndicator); 66 | mAnimatorIn.start(); 67 | } 68 | 69 | View selectedIndicator = getChildAt(position); 70 | if (selectedIndicator != null) { 71 | selectedIndicator.setBackgroundDrawable(mIndicatorBackgroundRes); 72 | mAnimatorOut.setTarget(selectedIndicator); 73 | mAnimatorOut.start(); 74 | } 75 | mLastPosition = position; 76 | } 77 | 78 | @Override 79 | public void onPageScrollStateChanged(int state) { 80 | } 81 | }; 82 | private DataSetObserver mInternalDataSetObserver = new DataSetObserver() { 83 | @Override 84 | public void onChanged() { 85 | super.onChanged(); 86 | if (mViewpager == null) { 87 | return; 88 | } 89 | 90 | int newCount = mViewpager.getAdapter().getCount(); 91 | int currentCount = getChildCount(); 92 | 93 | if (newCount == currentCount) { // No change 94 | return; 95 | } else if (mLastPosition < newCount) { 96 | mLastPosition = mViewpager.getCurrentItem(); 97 | } else { 98 | mLastPosition = -1; 99 | } 100 | 101 | createIndicators(); 102 | } 103 | }; 104 | 105 | public RoundPageIndicator(Context context) { 106 | super(context); 107 | init(context, null); 108 | } 109 | 110 | public RoundPageIndicator(Context context, AttributeSet attrs) { 111 | super(context, attrs); 112 | init(context, attrs); 113 | } 114 | 115 | public RoundPageIndicator(Context context, AttributeSet attrs, int defStyleAttr) { 116 | super(context, attrs, defStyleAttr); 117 | init(context, attrs); 118 | } 119 | 120 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 121 | public RoundPageIndicator(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 122 | super(context, attrs, defStyleAttr, defStyleRes); 123 | init(context, attrs); 124 | } 125 | 126 | private void init(Context context, AttributeSet attrs) { 127 | checkIndicatorConfig(context); 128 | if (!isInEditMode()) { 129 | mIndicatorBackgroundRes = context.getResources().getDrawable(R.drawable.white_radius).mutate(); 130 | mIndicatorUnselectedBackgroundRes = mIndicatorBackgroundRes.mutate(); 131 | mIndicatorUnselectedBackgroundRes.setAlpha(127); 132 | } 133 | } 134 | 135 | /** 136 | * 有几个不跟主题走的页面,需要调用该方法 137 | */ 138 | public void omitTheme() { 139 | mIndicatorBackgroundRes = getContext().getResources().getDrawable(R.drawable.white_radius); 140 | mIndicatorUnselectedBackgroundRes = mIndicatorBackgroundRes.mutate(); 141 | mIndicatorUnselectedBackgroundRes.setAlpha(127); 142 | } 143 | 144 | /** 145 | * Create and configure Indicator in Java code. 146 | */ 147 | public void configureIndicator(int indicatorWidth, int indicatorHeight, int indicatorMargin) { 148 | configureIndicator(indicatorWidth, indicatorHeight, indicatorMargin, 149 | R.animator.scale_with_alpha, 0, R.drawable.white_radius, R.drawable.white_radius); 150 | } 151 | 152 | public void configureIndicator(int indicatorWidth, int indicatorHeight, int indicatorMargin, 153 | @AnimatorRes int animatorId, @AnimatorRes int animatorReverseId, 154 | @DrawableRes int indicatorBackgroundId, 155 | @DrawableRes int indicatorUnselectedBackgroundId) { 156 | 157 | mIndicatorWidth = indicatorWidth; 158 | mIndicatorHeight = indicatorHeight; 159 | mIndicatorMargin = indicatorMargin; 160 | 161 | mAnimatorResId = animatorId; 162 | mAnimatorReverseResId = animatorReverseId; 163 | // mIndicatorBackgroundRes = indicatorBackgroundId; 164 | // mIndicatorUnselectedBackgroundRes = indicatorUnselectedBackgroundId; 165 | 166 | checkIndicatorConfig(getContext()); 167 | } 168 | 169 | private void checkIndicatorConfig(Context context) { 170 | mIndicatorWidth = (mIndicatorWidth < 0) ? dip2px(DEFAULT_INDICATOR_WIDTH) : mIndicatorWidth; 171 | mIndicatorHeight = 172 | (mIndicatorHeight < 0) ? dip2px(DEFAULT_INDICATOR_WIDTH) : mIndicatorHeight; 173 | mIndicatorMargin = 174 | (mIndicatorMargin < 0) ? dip2px(DEFAULT_INDICATOR_WIDTH) : mIndicatorMargin; 175 | 176 | mAnimatorResId = (mAnimatorResId == 0) ? R.animator.scale_with_alpha : mAnimatorResId; 177 | 178 | mAnimatorOut = createAnimatorOut(context); 179 | mImmediateAnimatorOut = createAnimatorOut(context); 180 | mImmediateAnimatorOut.setDuration(0); 181 | 182 | mAnimatorIn = createAnimatorIn(context); 183 | mImmediateAnimatorIn = createAnimatorIn(context); 184 | mImmediateAnimatorIn.setDuration(0); 185 | 186 | // mIndicatorBackgroundRes = (mIndicatorBackgroundRes == 0) ? R.drawable.white_radius 187 | // : mIndicatorBackgroundRes; 188 | // mIndicatorUnselectedBackgroundRes = 189 | // (mIndicatorUnselectedBackgroundRes == 0) ? mIndicatorBackgroundRes 190 | // : mIndicatorUnselectedBackgroundRes; 191 | } 192 | 193 | private Animator createAnimatorOut(Context context) { 194 | return AnimatorInflater.loadAnimator(context, mAnimatorResId); 195 | } 196 | 197 | private Animator createAnimatorIn(Context context) { 198 | Animator animatorIn; 199 | if (mAnimatorReverseResId == 0) { 200 | animatorIn = AnimatorInflater.loadAnimator(context, mAnimatorResId); 201 | animatorIn.setInterpolator(new ReverseInterpolator()); 202 | } else { 203 | animatorIn = AnimatorInflater.loadAnimator(context, mAnimatorReverseResId); 204 | } 205 | return animatorIn; 206 | } 207 | 208 | public void setViewPager(ViewPager viewPager) { 209 | mViewpager = viewPager; 210 | if (mViewpager != null && mViewpager.getAdapter() != null) { 211 | mLastPosition = -1; 212 | createIndicators(); 213 | mViewpager.removeOnPageChangeListener(mInternalPageChangeListener); 214 | mViewpager.addOnPageChangeListener(mInternalPageChangeListener); 215 | mInternalPageChangeListener.onPageSelected(mViewpager.getCurrentItem()); 216 | } 217 | } 218 | 219 | public DataSetObserver getDataSetObserver() { 220 | return mInternalDataSetObserver; 221 | } 222 | 223 | /** 224 | * @deprecated User ViewPager addOnPageChangeListener 225 | */ 226 | @Deprecated 227 | public void setOnPageChangeListener(OnPageChangeListener onPageChangeListener) { 228 | if (mViewpager == null) { 229 | throw new NullPointerException("can not find Viewpager , setViewPager first"); 230 | } 231 | mViewpager.removeOnPageChangeListener(onPageChangeListener); 232 | mViewpager.addOnPageChangeListener(onPageChangeListener); 233 | } 234 | 235 | private void createIndicators() { 236 | removeAllViews(); 237 | int count = mViewpager.getAdapter().getCount(); 238 | if (count <= 0) { 239 | return; 240 | } 241 | int currentItem = mViewpager.getCurrentItem(); 242 | int orientation = getOrientation(); 243 | 244 | for (int i = 0; i < count; i++) { 245 | if (currentItem == i) { 246 | addIndicator(orientation, mIndicatorBackgroundRes, mImmediateAnimatorOut); 247 | } else { 248 | addIndicator(orientation, mIndicatorUnselectedBackgroundRes, 249 | mImmediateAnimatorIn); 250 | } 251 | } 252 | } 253 | 254 | private void addIndicator(int orientation, Drawable backgroundDrawableId, 255 | Animator animator) { 256 | if (animator.isRunning()) { 257 | animator.end(); 258 | animator.cancel(); 259 | } 260 | 261 | View Indicator = new View(getContext()); 262 | Indicator.setBackgroundDrawable(backgroundDrawableId); 263 | addView(Indicator, mIndicatorWidth, mIndicatorHeight); 264 | LayoutParams lp = (LayoutParams) Indicator.getLayoutParams(); 265 | 266 | if (orientation == HORIZONTAL) { 267 | lp.leftMargin = mIndicatorMargin; 268 | lp.rightMargin = mIndicatorMargin; 269 | } else { 270 | lp.topMargin = mIndicatorMargin; 271 | lp.bottomMargin = mIndicatorMargin; 272 | } 273 | 274 | Indicator.setLayoutParams(lp); 275 | 276 | animator.setTarget(Indicator); 277 | animator.start(); 278 | } 279 | 280 | public int dip2px(float dpValue) { 281 | final float scale = getResources().getDisplayMetrics().density; 282 | return (int) (dpValue * scale + 0.5f); 283 | } 284 | 285 | private class ReverseInterpolator implements Interpolator { 286 | @Override 287 | public float getInterpolation(float value) { 288 | return Math.abs(1.0f - value); 289 | } 290 | } 291 | } -------------------------------------------------------------------------------- /app/src/main/java/com/demo/imagetransdemo/view/SpaceDecoration.java: -------------------------------------------------------------------------------- 1 | package com.demo.imagetransdemo.view; 2 | 3 | import android.graphics.Rect; 4 | import android.support.v7.widget.GridLayoutManager; 5 | import android.support.v7.widget.LinearLayoutManager; 6 | import android.support.v7.widget.OrientationHelper; 7 | import android.support.v7.widget.RecyclerView; 8 | import android.support.v7.widget.StaggeredGridLayoutManager; 9 | import android.view.Gravity; 10 | import android.view.View; 11 | 12 | 13 | public class SpaceDecoration extends RecyclerView.ItemDecoration { 14 | 15 | private int halfSpace; 16 | private int headerCount = -1; 17 | private int footerCount = Integer.MAX_VALUE; 18 | private boolean mPaddingEdgeSide = true; 19 | private boolean mPaddingStart = true; 20 | private boolean mPaddingHeaderFooter = false; 21 | 22 | 23 | public SpaceDecoration(int space) { 24 | this.halfSpace = space / 2; 25 | } 26 | 27 | public void setPaddingEdgeSide(boolean mPaddingEdgeSide) { 28 | this.mPaddingEdgeSide = mPaddingEdgeSide; 29 | } 30 | 31 | public void setPaddingStart(boolean mPaddingStart) { 32 | this.mPaddingStart = mPaddingStart; 33 | } 34 | 35 | public void setPaddingHeaderFooter(boolean mPaddingHeaderFooter) { 36 | this.mPaddingHeaderFooter = mPaddingHeaderFooter; 37 | } 38 | 39 | @Override 40 | public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { 41 | int position = parent.getChildAdapterPosition(view); 42 | int spanCount = 0; 43 | int orientation = 0; 44 | int spanIndex = 0; 45 | int headerCount = 0,footerCount = 0; 46 | 47 | RecyclerView.LayoutManager layoutManager = parent.getLayoutManager(); 48 | if (layoutManager instanceof StaggeredGridLayoutManager){ 49 | orientation = ((StaggeredGridLayoutManager) layoutManager).getOrientation(); 50 | spanCount = ((StaggeredGridLayoutManager) layoutManager).getSpanCount(); 51 | spanIndex = ((StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams()).getSpanIndex(); 52 | }else if (layoutManager instanceof GridLayoutManager){ 53 | orientation = ((GridLayoutManager) layoutManager).getOrientation(); 54 | spanCount = ((GridLayoutManager) layoutManager).getSpanCount(); 55 | spanIndex = ((GridLayoutManager.LayoutParams) view.getLayoutParams()).getSpanIndex(); 56 | }else if (layoutManager instanceof LinearLayoutManager){ 57 | orientation = ((LinearLayoutManager) layoutManager).getOrientation(); 58 | spanCount = 1; 59 | spanIndex = 0; 60 | } 61 | 62 | /** 63 | * 普通Item的尺寸 64 | */ 65 | if ((position>=headerCount&&position1)gravity = Gravity.LEFT; 68 | else if (spanIndex == spanCount-1&&spanCount>1)gravity = Gravity.RIGHT; 69 | else if (spanCount == 1)gravity = Gravity.FILL_HORIZONTAL; 70 | else { 71 | gravity = Gravity.CENTER; 72 | } 73 | if (orientation == OrientationHelper.VERTICAL){ 74 | switch (gravity){ 75 | case Gravity.LEFT: 76 | if (mPaddingEdgeSide) 77 | outRect.left = halfSpace*2; 78 | outRect.right = halfSpace; 79 | break; 80 | case Gravity.RIGHT: 81 | outRect.left = halfSpace; 82 | if (mPaddingEdgeSide) 83 | outRect.right = halfSpace*2; 84 | break; 85 | case Gravity.FILL_HORIZONTAL: 86 | if (mPaddingEdgeSide){ 87 | outRect.left = halfSpace*2; 88 | outRect.right = halfSpace*2; 89 | } 90 | break; 91 | case Gravity.CENTER: 92 | outRect.left = halfSpace; 93 | outRect.right = halfSpace; 94 | break; 95 | } 96 | if (position - headerCount < spanCount && mPaddingStart)outRect.top = halfSpace*2; 97 | outRect.bottom = halfSpace*2; 98 | }else { 99 | switch (gravity){ 100 | case Gravity.LEFT: 101 | if (mPaddingEdgeSide) 102 | outRect.bottom = halfSpace*2; 103 | outRect.top = halfSpace; 104 | break; 105 | case Gravity.RIGHT: 106 | outRect.bottom = halfSpace; 107 | if (mPaddingEdgeSide) 108 | outRect.top = halfSpace*2; 109 | break; 110 | case Gravity.FILL_HORIZONTAL: 111 | if (mPaddingEdgeSide){ 112 | outRect.left = halfSpace*2; 113 | outRect.right = halfSpace*2; 114 | } 115 | break; 116 | case Gravity.CENTER: 117 | outRect.bottom = halfSpace; 118 | outRect.top = halfSpace; 119 | break; 120 | } 121 | if (position - headerCount < spanCount && mPaddingStart)outRect.left = halfSpace*2; 122 | outRect.right = halfSpace*2; 123 | } 124 | }else {//只有HeaderFooter进到这里 125 | if (mPaddingHeaderFooter) {//并且需要padding Header&Footer 126 | if (orientation == OrientationHelper.VERTICAL){ 127 | if (mPaddingEdgeSide) { 128 | outRect.left = halfSpace * 2; 129 | outRect.right = halfSpace * 2; 130 | } 131 | outRect.top = halfSpace*2; 132 | }else{ 133 | if (mPaddingEdgeSide) { 134 | outRect.top = halfSpace * 2; 135 | outRect.bottom = halfSpace * 2; 136 | } 137 | outRect.left = halfSpace*2; 138 | } 139 | } 140 | } 141 | } 142 | 143 | 144 | } -------------------------------------------------------------------------------- /app/src/main/res/animator/scale_with_alpha.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 11 | 16 | 17 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/button.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_arrow_down.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_close.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_like.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_more.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_replay.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_repost.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/place_holder.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/white_radius.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | 23 | 24 | 28 | 29 | 34 | 35 | 40 | 41 | 47 | 48 | 49 | 55 | 56 | 61 | 62 | 63 | 68 | 69 | 70 | 76 | 77 |