├── .gitignore ├── .idea ├── gradle.xml ├── inspectionProfiles │ └── Project_Default.xml ├── markdown-navigator.xml ├── markdown-navigator │ └── profiles_settings.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── Lib_BitmapKit ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── danxx │ │ └── bitmapkit │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── danxx │ │ │ └── bitmapkit │ │ │ ├── BitmapKitConfig.java │ │ │ ├── ShaderRoundUtil.java │ │ │ ├── ShaderRoundUtils.java │ │ │ ├── blur │ │ │ ├── BitmapBlurUtil.java │ │ │ ├── BlurKit.java │ │ │ └── ShadeUtil.java │ │ │ ├── compress │ │ │ ├── CompressUtil.java │ │ │ └── Compressor.java │ │ │ ├── crop │ │ │ └── BitmapCropUtil.java │ │ │ ├── draw │ │ │ └── BitmapDrawUtil.java │ │ │ ├── rotate │ │ │ └── BitmapRotateUtil.java │ │ │ └── scale │ │ │ └── BitmapScaleUtil.java │ └── res │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── danxx │ └── bitmapkit │ └── ExampleUnitTest.java ├── Lib_ImageLoader ├── .gitignore ├── README.md ├── build.gradle ├── img │ ├── dximageloadimg.png │ └── flow.png ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── dximageloader │ │ ├── DiskLruCache.java │ │ └── DxImageLoader.java │ └── res │ └── values │ └── strings.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── danxx │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── danxx │ │ │ ├── ActivityCrop.java │ │ │ ├── ActivitySnapRecycler.java │ │ │ ├── DanxxApp.java │ │ │ ├── MainActivity.java │ │ │ ├── demoview │ │ │ ├── BlurImageView.java │ │ │ ├── CropImageView.java │ │ │ └── PhotoDraweeView.java │ │ │ ├── utils │ │ │ └── BitmapClipUtils.java │ │ │ └── view │ │ │ ├── RoundBlurShaderView.java │ │ │ └── ShadowDraweeView.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable-xhdpi │ │ └── gaoyuanyuan.jpeg │ │ ├── drawable │ │ ├── bitmap_clip.png │ │ ├── ic_launcher.png │ │ ├── ic_launcher_background.xml │ │ ├── loading.png │ │ └── shadow_5.9.png │ │ ├── layout │ │ ├── activity_crop.xml │ │ ├── activity_main.xml │ │ ├── activity_snap_recycler.xml │ │ └── layout_recycler_item.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── danxx │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 93 | -------------------------------------------------------------------------------- /.idea/markdown-navigator.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 36 | 37 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /.idea/markdown-navigator/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 26 | 27 | 28 | 29 | 30 | 31 | 33 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Lib_BitmapKit/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /Lib_BitmapKit/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'com.jakewharton.hugo' 3 | 4 | android { 5 | compileSdkVersion 26 6 | 7 | 8 | 9 | defaultConfig { 10 | minSdkVersion 15 11 | targetSdkVersion 26 12 | versionCode 1 13 | versionName "1.0" 14 | 15 | 16 | renderscriptTargetApi 25 17 | renderscriptSupportModeEnabled true 18 | 19 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 20 | 21 | } 22 | 23 | buildTypes { 24 | release { 25 | minifyEnabled false 26 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 27 | } 28 | } 29 | 30 | } 31 | 32 | dependencies { 33 | implementation fileTree(dir: 'libs', include: ['*.jar']) 34 | 35 | implementation 'com.android.support:appcompat-v7:26.1.0' 36 | testImplementation 'junit:junit:4.12' 37 | androidTestImplementation 'com.android.support.test:runner:1.0.1' 38 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' 39 | 40 | api 'com.amitshekhar.android:glide-bitmap-pool:0.0.1' 41 | 42 | api 'io.reactivex.rxjava2:rxjava:2.1.8' 43 | } 44 | -------------------------------------------------------------------------------- /Lib_BitmapKit/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /Lib_BitmapKit/src/androidTest/java/danxx/bitmapkit/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package danxx.bitmapkit; 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 | * Instrumented 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("danxx.bitmapkit.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Lib_BitmapKit/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /Lib_BitmapKit/src/main/java/danxx/bitmapkit/BitmapKitConfig.java: -------------------------------------------------------------------------------- 1 | package danxx.bitmapkit; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | 6 | import com.glidebitmappool.GlideBitmapPool; 7 | 8 | import danxx.bitmapkit.blur.BlurKit; 9 | 10 | /** 11 | * Created by danxx on 2018/1/15. 12 | */ 13 | 14 | public class BitmapKitConfig { 15 | 16 | /** 17 | * 默认的Bitmap存储格式 18 | */ 19 | public static final Bitmap.Config DEFAULT_CONFIG = Bitmap.Config.ARGB_8888; 20 | 21 | /** 22 | * 默认的Bitmap对象缓存大小 23 | */ 24 | private static final int BITMAP_POOL_MAX_SIZE = 2 * 1024 * 1024; 25 | 26 | /** 27 | * 工具箱初始化 28 | * @param context 29 | * @param bitmapPoolMaxSize 30 | */ 31 | public static void initialize(Context context, int bitmapPoolMaxSize) { 32 | 33 | /** 34 | * 模糊工具初始化 35 | */ 36 | BlurKit.init(context); 37 | 38 | if(bitmapPoolMaxSize < BITMAP_POOL_MAX_SIZE){ 39 | bitmapPoolMaxSize = BITMAP_POOL_MAX_SIZE; 40 | } 41 | 42 | /** 43 | * bitmap 复用池最大量 44 | */ 45 | GlideBitmapPool.initialize(bitmapPoolMaxSize); 46 | 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /Lib_BitmapKit/src/main/java/danxx/bitmapkit/ShaderRoundUtil.java: -------------------------------------------------------------------------------- 1 | package danxx.bitmapkit; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.BitmapShader; 5 | import android.graphics.Canvas; 6 | import android.graphics.Color; 7 | import android.graphics.ComposeShader; 8 | import android.graphics.LinearGradient; 9 | import android.graphics.Matrix; 10 | import android.graphics.Paint; 11 | import android.graphics.PorterDuff; 12 | import android.graphics.PorterDuffXfermode; 13 | import android.graphics.Rect; 14 | import android.graphics.RectF; 15 | import android.graphics.Shader; 16 | import android.util.Log; 17 | 18 | import com.glidebitmappool.GlideBitmapPool; 19 | 20 | import danxx.bitmapkit.blur.BlurKit; 21 | import hugo.weaving.DebugLog; 22 | 23 | /** 24 | * @author danxx 25 | * @date 2018/1/12 26 | * @desc 阴影圆角工具 27 | */ 28 | 29 | public class ShaderRoundUtil { 30 | 31 | /** 32 | * 33 | * @param canvas 当前view的画布 34 | * @param bitmap 阴影处理图片 35 | * @param mRadius 圆角半径 36 | * @param currentRect 当前view的绘制矩形 37 | * @param shadowHeight 下面要绘制阴影的高度 38 | * @return 39 | */ 40 | @DebugLog 41 | public static void drawRoundBlurShader(Canvas canvas, Bitmap bitmap, int mRadius,Rect currentRect,int shadowHeight){ 42 | Bitmap rbsBitmap = null; 43 | if(bitmap == null){ 44 | return; 45 | } 46 | 47 | Paint paint = new Paint(); 48 | // paint.setAlpha(30); 49 | /**把图片裁剪成阴影高度*/ 50 | rbsBitmap = clipBitmapBottom(bitmap,shadowHeight,0.6f); 51 | 52 | rbsBitmap = BlurKit.getInstance().blur(rbsBitmap, 18); 53 | 54 | RectF shadowRect = new RectF(currentRect); 55 | shadowRect.bottom = shadowHeight; 56 | 57 | shadowRect.offset(0,(currentRect.height() - (shadowHeight/2))); 58 | 59 | /**渐变Bitmap*/ 60 | BitmapShader bitmapShader = new BitmapShader(rbsBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); 61 | 62 | LinearGradient linearGradient = new LinearGradient(0,shadowRect.top,0,shadowRect.bottom, Color.BLACK, Color.TRANSPARENT,Shader.TileMode.CLAMP); 63 | 64 | ComposeShader composeShader = new ComposeShader(bitmapShader,linearGradient,new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); 65 | 66 | paint.setShader(composeShader); 67 | 68 | /**绘制圆角矩形*/ 69 | canvas.drawRoundRect(shadowRect ,mRadius ,mRadius ,paint); 70 | 71 | } 72 | 73 | 74 | /** 75 | * 返回一个抠图模糊渐变圆角图片 76 | * @param srcBitmap 阴影处理图片 77 | * @param mRadius 圆角半径 78 | * @param currentRect 当前view的绘制矩形 79 | * @param shadowHeight 下面要绘制阴影的高度 80 | * @return 81 | */ 82 | @DebugLog 83 | public static Bitmap processRoundBlurShader(Canvas canvas,Bitmap srcBitmap, int mRadius,Rect currentRect,int shadowHeight){ 84 | Bitmap rbsBitmap = null; 85 | if(srcBitmap == null){ 86 | Log.d("danxx", "return rbsBitmap == null"); 87 | return srcBitmap; 88 | } 89 | Log.d("danxx", "processRoundBlurShader..."); 90 | 91 | 92 | Paint paint = new Paint(); 93 | //取消透明 94 | //paint.setAlpha(30); 95 | /**把图片裁剪成阴影高度*/ 96 | rbsBitmap = clipBitmapBottom(srcBitmap,shadowHeight,0.6f); 97 | 98 | rbsBitmap = BlurKit.getInstance().blur(rbsBitmap, 18); 99 | 100 | RectF shadowRect = new RectF(currentRect); 101 | shadowRect.bottom = shadowHeight; 102 | //取消偏移 103 | //shadowRect.offset(0,(currentRect.height() - (shadowHeight/2))); 104 | 105 | /**临时画布*/ 106 | Canvas tempCanvas = new Canvas(); 107 | Bitmap tempBitmap/* = Bitmap.createBitmap((int)shadowRect.width(), (int)shadowRect.height(), Bitmap.Config.ARGB_8888)*/; 108 | tempBitmap = GlideBitmapPool.getDirtyBitmap((int)shadowRect.width(), (int)shadowRect.height(), Bitmap.Config.ARGB_4444); 109 | tempCanvas.setBitmap(tempBitmap); 110 | 111 | /**渐变Bitmap*/ 112 | BitmapShader bitmapShader = new BitmapShader(rbsBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); 113 | /**线性渐变*/ 114 | LinearGradient linearGradient = new LinearGradient(0,shadowRect.top,0,shadowRect.bottom, Color.BLACK, Color.TRANSPARENT,Shader.TileMode.CLAMP); 115 | /**混合*/ 116 | ComposeShader composeShader = new ComposeShader(bitmapShader,linearGradient,new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); 117 | 118 | paint.setShader(composeShader); 119 | 120 | /**绘制圆角矩形*/ 121 | tempCanvas.drawRoundRect(shadowRect ,mRadius ,mRadius ,paint); 122 | 123 | /**回收之前的Bitmap*/ 124 | if (srcBitmap != null && !srcBitmap.equals(tempBitmap) && !srcBitmap.isRecycled()) { 125 | // srcBitmap.recycle(); 126 | GlideBitmapPool.putBitmap(srcBitmap); 127 | } 128 | return tempBitmap; 129 | } 130 | 131 | /** 132 | * 裁剪一定高度保留下面 133 | * @param srcBitmap 134 | * @param needHeight 135 | * @return 136 | */ 137 | @DebugLog 138 | private static Bitmap clipBitmapBottom(Bitmap srcBitmap, int needHeight) { 139 | 140 | Log.d("danxx", "clipBitmapBottom before h : "+srcBitmap.getHeight()); 141 | 142 | /**裁剪保留下部分的第一个像素的Y坐标*/ 143 | int needY = srcBitmap.getHeight() - needHeight; 144 | 145 | /**裁剪关键步骤*/ 146 | Bitmap clipBitmap = Bitmap.createBitmap(srcBitmap,0,needY,srcBitmap.getWidth(),needHeight); 147 | 148 | Log.d("danxx", "clipBitmapBottom after h : "+clipBitmap.getHeight()); 149 | 150 | /**回收之前的Bitmap*/ 151 | if (srcBitmap != null && !srcBitmap.equals(clipBitmap) && !srcBitmap.isRecycled()) { 152 | // srcBitmap.recycle(); 153 | GlideBitmapPool.putBitmap(srcBitmap); 154 | } 155 | 156 | return clipBitmap; 157 | } 158 | 159 | /** 160 | * 裁剪一定高度并缩放保留下面 161 | * @param srcBitmap 原图 162 | * @param needHeight 裁剪后需要的高度 163 | * @param scaleValue 裁剪后的缩放 164 | * @return 165 | */ 166 | @DebugLog 167 | private static Bitmap clipBitmapBottom(Bitmap srcBitmap, int needHeight, float scaleValue) { 168 | 169 | Log.d("danxx", "clipBitmapBottom before h : "+srcBitmap.getHeight()); 170 | 171 | /**裁剪保留下部分的第一个像素的Y坐标*/ 172 | int needY = srcBitmap.getHeight() - needHeight; 173 | 174 | /**缩放矩阵*/ 175 | Matrix scaleMatrix = new Matrix(); 176 | scaleMatrix.postScale(scaleValue, scaleValue); 177 | 178 | /**裁剪关键步骤*/ 179 | Bitmap clipBitmap = Bitmap.createBitmap(srcBitmap,0,needY,srcBitmap.getWidth(),needHeight, scaleMatrix, true); 180 | 181 | Log.d("danxx", "clipBitmapBottom after h : "+clipBitmap.getHeight()); 182 | 183 | /**回收之前的Bitmap*/ 184 | if (srcBitmap != null && !srcBitmap.equals(clipBitmap) && !srcBitmap.isRecycled()) { 185 | // srcBitmap.recycle(); 186 | GlideBitmapPool.putBitmap(srcBitmap); 187 | } 188 | 189 | return clipBitmap; 190 | } 191 | 192 | /** 193 | * Bitmap绘制成圆角返回 194 | * @param bitmap 195 | * @param mRadius 196 | * @return 197 | */ 198 | private Bitmap createRoundBitmap(Bitmap bitmap, int mRadius){ 199 | if(bitmap == null){ 200 | return null; 201 | } 202 | 203 | int mWidth = bitmap.getWidth(); 204 | int mHeight = bitmap.getHeight(); 205 | 206 | final Paint paint = new Paint(); 207 | /**开启抗锯齿**/ 208 | paint.setAntiAlias(true); 209 | /****/ 210 | Bitmap target = Bitmap.createBitmap(mWidth,mHeight, Bitmap.Config.ARGB_8888); 211 | /** 212 | * Construct a canvas with the specified bitmap to draw into. The bitmapmust be mutable 213 | * 以bitmap对象创建一个画布,则将内容都绘制在bitmap上,bitmap不得为null; 214 | */ 215 | Canvas canvas = new Canvas(target); 216 | /**新建一个矩形绘制区域,并给出左上角和右下角的坐标**/ 217 | RectF rect = new RectF(0 , 0 ,mWidth ,mHeight); 218 | /** 219 | * 把图片缩放成我们想要的大小 220 | */ 221 | bitmap = Bitmap.createScaledBitmap(bitmap,mWidth,mHeight,false); 222 | /**在绘制矩形区域绘制用画笔绘制一个圆角矩形**/ 223 | canvas.drawRoundRect(rect ,mRadius ,mRadius ,paint); 224 | /** 225 | * 我简单理解为设置画笔在绘制时图形堆叠时候的显示模式 226 | * SRC_IN:取两层绘制交集。显示上层。 227 | */ 228 | paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); 229 | canvas.drawBitmap(bitmap ,0 ,0 ,paint); 230 | 231 | /**回收之前的Bitmap*/ 232 | if (bitmap != null && !bitmap.equals(target) && !bitmap.isRecycled()) { 233 | // bitmap.recycle(); 234 | GlideBitmapPool.putBitmap(bitmap); 235 | } 236 | 237 | /****/ 238 | return target; 239 | } 240 | 241 | } 242 | -------------------------------------------------------------------------------- /Lib_BitmapKit/src/main/java/danxx/bitmapkit/ShaderRoundUtils.java: -------------------------------------------------------------------------------- 1 | package danxx.bitmapkit; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.BitmapShader; 5 | import android.graphics.Canvas; 6 | import android.graphics.Color; 7 | import android.graphics.ComposeShader; 8 | import android.graphics.LinearGradient; 9 | import android.graphics.Paint; 10 | import android.graphics.PorterDuff; 11 | import android.graphics.PorterDuffXfermode; 12 | import android.graphics.Rect; 13 | import android.graphics.RectF; 14 | import android.graphics.Shader; 15 | import android.util.Log; 16 | 17 | import com.glidebitmappool.GlideBitmapPool; 18 | 19 | import danxx.bitmapkit.blur.BlurKit; 20 | import hugo.weaving.DebugLog; 21 | 22 | /** 23 | * @author danxx 24 | * @date 2018/1/12 25 | * @desc 阴影圆角工具 26 | */ 27 | 28 | public class ShaderRoundUtils { 29 | 30 | /** 31 | * 返回一个抠图模糊渐变圆角图片 32 | * 33 | * @param srcBitmap 阴影处理图片 34 | * @param mRadius 圆角半径 35 | * @param currentRect 当前view的绘制矩形 36 | * @param shadowHeight 下面要绘制阴影的高度 37 | * @param clipRecycle srcBitmap原图裁剪后是否需要回收调 38 | */ 39 | @DebugLog 40 | public static Bitmap processRoundBlurShader(Bitmap srcBitmap, int mRadius, Rect currentRect, 41 | int shadowHeight, boolean clipRecycle) { 42 | if (srcBitmap == null) { 43 | Log.d("danxx", "return rbsBitmap == null"); 44 | return srcBitmap; 45 | } 46 | Log.d("danxx", "processRoundBlurShader..."); 47 | 48 | Paint paint = new Paint(); 49 | //取消透明 50 | //paint.setAlpha(20); 51 | /**把图片裁剪成阴影高度*/ 52 | 53 | Log.i("danxx", "crop"); 54 | 55 | Bitmap clipBitmap = clipBitmapBottom(srcBitmap, shadowHeight, clipRecycle); 56 | 57 | Bitmap blurBitmap = BlurKit.getInstance().blur(clipBitmap, 18); 58 | 59 | Log.i("danxx", "blurBitmap : " + blurBitmap.getHeight()); 60 | 61 | RectF shadowRect = new RectF(currentRect); 62 | 63 | Log.i("danxx", "blurBitmap 1: " + shadowRect.height()); 64 | 65 | shadowRect.bottom = blurBitmap.getHeight(); 66 | 67 | Log.i("danxx", "blurBitmap 2: " + shadowRect.height()); 68 | 69 | //取消偏移 70 | //shadowRect.offset(0,(currentRect.height() - (shadowHeight/2))); 71 | 72 | /**临时画布*/ 73 | 74 | // Bitmap tempBitmap = 75 | // Bitmap.createBitmap(blurBitmap.getWidth(), blurBitmap.getHeight(), Bitmap.Config.ARGB_8888); 76 | Bitmap tempBitmap = 77 | GlideBitmapPool.getBitmap(blurBitmap.getWidth(), blurBitmap.getHeight(), Bitmap.Config.ARGB_8888); 78 | Canvas tempCanvas = new Canvas(tempBitmap); 79 | 80 | /**渐变Bitmap*/ 81 | BitmapShader bitmapShader = 82 | new BitmapShader(blurBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); 83 | /**线性渐变*/ 84 | LinearGradient linearGradient = 85 | new LinearGradient(0, shadowRect.top, 0, shadowRect.bottom, Color.BLACK, Color.TRANSPARENT, 86 | Shader.TileMode.CLAMP); 87 | /**混合*/ 88 | ComposeShader composeShader = new ComposeShader(bitmapShader, linearGradient, 89 | new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); 90 | 91 | paint.setShader(composeShader); 92 | 93 | /**绘制圆角矩形*/ 94 | tempCanvas.drawRoundRect(shadowRect, mRadius, mRadius, paint); 95 | 96 | /**回收之前的Bitmap*/ 97 | if (clipBitmap != null && !clipBitmap.equals(tempBitmap) && !clipBitmap.isRecycled()) { 98 | Log.d("danxx","GlideBitmapPool 111 recycle : "+clipBitmap); 99 | // clipBitmap.recycle(); 100 | GlideBitmapPool.putBitmap(clipBitmap); 101 | } 102 | return tempBitmap; 103 | } 104 | 105 | /** 106 | * 裁剪一定高度并缩放保留下面 107 | * 108 | * @param srcBitmap 原图 109 | * @param needHeight 裁剪后需要的高度 110 | * @param clipRecycle 原图裁剪后是否需要回收 111 | */ 112 | @DebugLog 113 | private static Bitmap clipBitmapBottom(Bitmap srcBitmap, int needHeight,boolean clipRecycle) { 114 | 115 | // Bitmap cutBitmap = Bitmap.createBitmap(srcBitmap.getWidth(), needHeight, Bitmap.Config.ARGB_8888); 116 | Bitmap cutBitmap = GlideBitmapPool.getBitmap(srcBitmap.getWidth(), needHeight, Bitmap.Config.ARGB_8888); 117 | 118 | Canvas canvas = new Canvas(cutBitmap); 119 | 120 | Rect srcRect = 121 | new Rect(0, srcBitmap.getHeight()-needHeight, srcBitmap.getWidth(), srcBitmap.getHeight()); 122 | 123 | Rect desRect = new Rect(0, 0, srcBitmap.getWidth(), needHeight); 124 | 125 | canvas.drawBitmap(srcBitmap, srcRect, desRect, null); 126 | 127 | /**回收*/ 128 | if (clipRecycle && srcBitmap != null && !srcBitmap.equals(cutBitmap) && !srcBitmap.isRecycled()) { 129 | Log.d("danxx","GlideBitmapPool 333 recycle : "+srcBitmap); 130 | // srcBitmap.recycle(); 131 | GlideBitmapPool.putBitmap(srcBitmap); 132 | } 133 | 134 | return cutBitmap; 135 | } 136 | 137 | /** 138 | * @param canvas 当前view的画布 139 | * @param bitmap 阴影处理图片 140 | * @param currentRect 当前view的绘制矩形 141 | */ 142 | public static Bitmap drawRoundBlurShader(Canvas canvas, Bitmap bitmap, Rect currentRect) { 143 | Bitmap rbsBitmap = null; 144 | if (bitmap == null) { 145 | return rbsBitmap; 146 | } 147 | 148 | int shadowHeight = bitmap.getHeight(); 149 | 150 | RectF shadowRect = new RectF(currentRect); 151 | shadowRect.bottom = shadowHeight; 152 | 153 | Paint paint = new Paint(); 154 | 155 | shadowRect.offset(0, (currentRect.height() - (shadowHeight / 2))); 156 | 157 | /**绘制圆角矩形*/ 158 | canvas.drawBitmap(bitmap, null, shadowRect, paint); 159 | 160 | return rbsBitmap; 161 | } 162 | 163 | 164 | /** 165 | * Bitmap绘制成圆角返回 166 | */ 167 | private Bitmap createRoundBitmap(Bitmap bitmap, int mRadius) { 168 | if (bitmap == null) { 169 | return null; 170 | } 171 | 172 | int mWidth = bitmap.getWidth(); 173 | int mHeight = bitmap.getHeight(); 174 | 175 | final Paint paint = new Paint(); 176 | /**开启抗锯齿**/ 177 | paint.setAntiAlias(true); 178 | /****/ 179 | // Bitmap target = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888); 180 | Bitmap target = GlideBitmapPool.getBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888); 181 | 182 | /** 183 | * Construct a canvas with the specified bitmap to draw into. The bitmapmust be mutable 184 | * 以bitmap对象创建一个画布,则将内容都绘制在bitmap上,bitmap不得为null; 185 | */ 186 | Canvas canvas = new Canvas(target); 187 | /**新建一个矩形绘制区域,并给出左上角和右下角的坐标**/ 188 | RectF rect = new RectF(0, 0, mWidth, mHeight); 189 | /** 190 | * 把图片缩放成我们想要的大小 191 | */ 192 | bitmap = Bitmap.createScaledBitmap(bitmap, mWidth, mHeight, false); 193 | /**在绘制矩形区域绘制用画笔绘制一个圆角矩形**/ 194 | canvas.drawRoundRect(rect, mRadius, mRadius, paint); 195 | /** 196 | * 我简单理解为设置画笔在绘制时图形堆叠时候的显示模式 197 | * SRC_IN:取两层绘制交集。显示上层。 198 | */ 199 | paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); 200 | canvas.drawBitmap(bitmap, 0, 0, paint); 201 | /****/ 202 | return target; 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /Lib_BitmapKit/src/main/java/danxx/bitmapkit/blur/BitmapBlurUtil.java: -------------------------------------------------------------------------------- 1 | package danxx.bitmapkit.blur; 2 | 3 | /** 4 | * Created by danxx on 2018/1/28. 5 | * @Desc Bitmap发散模糊工具 6 | */ 7 | 8 | public class BitmapBlurUtil { 9 | 10 | 11 | 12 | } 13 | -------------------------------------------------------------------------------- /Lib_BitmapKit/src/main/java/danxx/bitmapkit/blur/BlurKit.java: -------------------------------------------------------------------------------- 1 | package danxx.bitmapkit.blur; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.Canvas; 6 | import android.graphics.Matrix; 7 | import android.support.v8.renderscript.Allocation; 8 | import android.support.v8.renderscript.Element; 9 | import android.support.v8.renderscript.RenderScript; 10 | import android.support.v8.renderscript.ScriptIntrinsicBlur; 11 | import android.view.View; 12 | 13 | import hugo.weaving.DebugLog; 14 | 15 | /** 16 | * Created by danxx on 2018/1/11. 17 | */ 18 | 19 | public class BlurKit { 20 | private static BlurKit instance; 21 | 22 | private RenderScript rs; 23 | 24 | public static void init(Context context) { 25 | if (instance != null) { 26 | return; 27 | } 28 | 29 | instance = new BlurKit(); 30 | instance.rs = RenderScript.create(context); 31 | } 32 | 33 | @DebugLog 34 | public Bitmap blur(Bitmap src, int radius) { 35 | final Allocation input = Allocation.createFromBitmap(rs, src); 36 | final Allocation output = Allocation.createTyped(rs, input.getType()); 37 | final ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs)); 38 | script.setRadius(radius); 39 | script.setInput(input); 40 | script.forEach(output); 41 | output.copyTo(src); 42 | return src; 43 | } 44 | 45 | public Bitmap blur(View src, int radius) { 46 | Bitmap bitmap = getBitmapForView(src, 1f); 47 | return blur(bitmap, radius); 48 | } 49 | 50 | public Bitmap fastBlur(View src, int radius, float downscaleFactor) { 51 | Bitmap bitmap = getBitmapForView(src, downscaleFactor); 52 | return blur(bitmap, radius); 53 | } 54 | 55 | public Bitmap getBitmapForView(View src, float downscaleFactor) { 56 | Bitmap bitmap = Bitmap.createBitmap( 57 | (int) (src.getWidth() * downscaleFactor), 58 | (int) (src.getHeight() * downscaleFactor), 59 | Bitmap.Config.ARGB_8888 60 | ); 61 | 62 | Canvas canvas = new Canvas(bitmap); 63 | Matrix matrix = new Matrix(); 64 | matrix.preScale(downscaleFactor, downscaleFactor); 65 | canvas.setMatrix(matrix); 66 | src.draw(canvas); 67 | 68 | return bitmap; 69 | } 70 | 71 | public static BlurKit getInstance() { 72 | if (instance == null) { 73 | throw new RuntimeException("BlurKit not initialized!"); 74 | } 75 | 76 | return instance; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Lib_BitmapKit/src/main/java/danxx/bitmapkit/blur/ShadeUtil.java: -------------------------------------------------------------------------------- 1 | package danxx.bitmapkit.blur; 2 | 3 | import android.content.Context; 4 | import android.content.res.Resources; 5 | import android.graphics.Bitmap; 6 | import android.graphics.Canvas; 7 | import android.graphics.Paint; 8 | import android.graphics.PorterDuff; 9 | import android.graphics.PorterDuffXfermode; 10 | import android.graphics.Rect; 11 | import android.graphics.drawable.Drawable; 12 | import android.util.DisplayMetrics; 13 | import android.util.Log; 14 | import android.util.TypedValue; 15 | 16 | import com.glidebitmappool.GlideBitmapPool; 17 | 18 | import java.lang.reflect.Field; 19 | 20 | import danxx.bitmapkit.scale.BitmapScaleUtil; 21 | import hugo.weaving.DebugLog; 22 | 23 | /** 24 | * @author danxx 25 | * @date 2018/1/12 26 | * @desc 阴影圆角工具 27 | */ 28 | 29 | public class ShadeUtil { 30 | 31 | /** 32 | * 给缩小的原图四周加上渐变的阴影 33 | * 34 | * @param srcBitmap 35 | * @param edgeSrcDrawable 36 | * @param create 阴影模糊图片是否创建新的 37 | * @return 38 | */ 39 | @DebugLog 40 | public static Bitmap createShadeBitmap(Canvas canvas, Bitmap srcBitmap, int shaderPadding, Drawable edgeSrcDrawable, Rect currentRect, int blurRadius, boolean create) { 41 | 42 | Paint paint = new Paint(); 43 | 44 | paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DARKEN)); 45 | 46 | paint.setDither(true); 47 | Bitmap blurBitmap = null; 48 | 49 | if (create) { 50 | 51 | Log.d("danxxx", "create : " + create); 52 | 53 | Bitmap bitmap = GlideBitmapPool.getBitmap(currentRect.width(), currentRect.height(), Bitmap.Config.ARGB_8888); 54 | 55 | Canvas drawCanvas = new Canvas(bitmap); 56 | 57 | Rect rect = new Rect(currentRect); 58 | //四周留出px画阴影 59 | rect.inset(currentRect.width()/6, currentRect.height()/6); 60 | //画图片 61 | drawCanvas.drawBitmap(srcBitmap, null, rect, paint); 62 | //画阴影圈 63 | // drawDrawableAt(drawCanvas, rect, edgeSrcDrawable); 64 | //模糊前缩小 65 | bitmap = BitmapScaleUtil.scaleBitmap(bitmap, 0.2f, 0.2f, true); 66 | //开始模糊 67 | blurBitmap = BlurKit.getInstance().blur(bitmap, blurRadius); 68 | //模糊后放大 69 | blurBitmap = BitmapScaleUtil.scaleBitmap(blurBitmap, 5.0f, 5.0f, true); 70 | 71 | } else { 72 | Log.d("danxxx", "create : " + create); 73 | blurBitmap = srcBitmap; 74 | } 75 | 76 | if (blurBitmap != null) { 77 | //下移 78 | // currentRect.offset(0, currentRect.height() / 4); 79 | // //左右放大 80 | // currentRect.inset(-currentRect.height() / 5, 0); 81 | canvas.drawBitmap(blurBitmap, null, currentRect, paint); 82 | } 83 | 84 | return blurBitmap; 85 | } 86 | 87 | public static int dip2px(Context context, float dpValue) { 88 | float scale = getMetrics(context.getResources()).density; 89 | return (int) (dpValue * scale + 0.5f); 90 | } 91 | 92 | public static float pt2px(Context context, float pt) { 93 | return TypedValue.applyDimension(3, pt, getMetrics(context.getResources())); 94 | } 95 | 96 | private static DisplayMetrics getMetrics(Resources res) { 97 | DisplayMetrics dm = null; 98 | if ("MiuiResources".equals(res.getClass().getSimpleName()) || "XResources".equals(res.getClass().getSimpleName())) { 99 | try { 100 | Field field = Resources.class.getDeclaredField("mTmpMetrics"); 101 | field.setAccessible(true); 102 | dm = (DisplayMetrics) field.get(res); 103 | } catch (Exception var3) { 104 | dm = null; 105 | } 106 | } 107 | 108 | return dm != null ? dm : res.getDisplayMetrics(); 109 | } 110 | 111 | public static void scaleRect(Rect rect, float ratioX, float ratioY) { 112 | if (null != rect) { 113 | int offsetX = (int) ((float) (rect.right - rect.left) * (ratioX - 1.0F) / 2.0F); 114 | int offsetY = (int) ((float) (rect.bottom - rect.top) * (ratioY - 1.0F) / 2.0F); 115 | rect.left -= offsetX; 116 | rect.right += offsetX; 117 | rect.top -= offsetY; 118 | rect.bottom += offsetY; 119 | } 120 | } 121 | 122 | @DebugLog 123 | public static void drawDrawableAt(Canvas canvas, Rect position, Drawable drawable) { 124 | if (null != canvas && null != position && null != drawable) { 125 | Rect padding = new Rect(); 126 | drawable.getPadding(padding); 127 | Rect bounds = new Rect(); 128 | bounds.left = position.left - padding.left; 129 | bounds.top = position.top - padding.top; 130 | bounds.right = position.right + padding.right; 131 | bounds.bottom = position.bottom + padding.bottom; 132 | drawable.setBounds(bounds); 133 | drawable.draw(canvas); 134 | } 135 | } 136 | 137 | } 138 | -------------------------------------------------------------------------------- /Lib_BitmapKit/src/main/java/danxx/bitmapkit/compress/CompressUtil.java: -------------------------------------------------------------------------------- 1 | package danxx.bitmapkit.compress; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.BitmapFactory; 5 | import android.graphics.Matrix; 6 | import android.media.ExifInterface; 7 | 8 | import java.io.File; 9 | import java.io.FileOutputStream; 10 | import java.io.IOException; 11 | 12 | /** 13 | * @Desc 图片压缩工具 14 | * Created by danxx on 2018/1/13. 15 | */ 16 | 17 | public class CompressUtil { 18 | 19 | private CompressUtil() { 20 | 21 | } 22 | 23 | static File compressImage(File imageFile, int reqWidth, int reqHeight, Bitmap.CompressFormat compressFormat, int quality, String destinationPath) throws IOException { 24 | FileOutputStream fileOutputStream = null; 25 | File file = new File(destinationPath).getParentFile(); 26 | if (!file.exists()) { 27 | file.mkdirs(); 28 | } 29 | try { 30 | fileOutputStream = new FileOutputStream(destinationPath); 31 | // write the compressed bitmap at the destination specified by destinationPath. 32 | decodeSampledBitmapFromFile(imageFile, reqWidth, reqHeight).compress(compressFormat, quality, fileOutputStream); 33 | } finally { 34 | if (fileOutputStream != null) { 35 | fileOutputStream.flush(); 36 | fileOutputStream.close(); 37 | } 38 | } 39 | 40 | return new File(destinationPath); 41 | } 42 | 43 | static Bitmap decodeSampledBitmapFromFile(File imageFile, int reqWidth, int reqHeight) throws IOException { 44 | // First decode with inJustDecodeBounds=true to check dimensions 45 | BitmapFactory.Options options = new BitmapFactory.Options(); 46 | options.inJustDecodeBounds = true; 47 | BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options); 48 | 49 | // Calculate inSampleSize 50 | options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); 51 | 52 | // Decode bitmap with inSampleSize set 53 | options.inJustDecodeBounds = false; 54 | 55 | Bitmap scaledBitmap = BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options); 56 | 57 | //check the rotation of the image and display it properly 58 | ExifInterface exif; 59 | exif = new ExifInterface(imageFile.getAbsolutePath()); 60 | int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 0); 61 | Matrix matrix = new Matrix(); 62 | if (orientation == 6) { 63 | matrix.postRotate(90); 64 | } else if (orientation == 3) { 65 | matrix.postRotate(180); 66 | } else if (orientation == 8) { 67 | matrix.postRotate(270); 68 | } 69 | scaledBitmap = Bitmap.createBitmap(scaledBitmap, 0, 0, scaledBitmap.getWidth(), scaledBitmap.getHeight(), matrix, true); 70 | return scaledBitmap; 71 | } 72 | 73 | private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { 74 | // Raw height and width of image 75 | final int height = options.outHeight; 76 | final int width = options.outWidth; 77 | int inSampleSize = 1; 78 | 79 | if (height > reqHeight || width > reqWidth) { 80 | 81 | final int halfHeight = height / 2; 82 | final int halfWidth = width / 2; 83 | 84 | // Calculate the largest inSampleSize value that is a power of 2 and keeps both 85 | // height and width larger than the requested height and width. 86 | while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) { 87 | inSampleSize *= 2; 88 | } 89 | } 90 | 91 | return inSampleSize; 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /Lib_BitmapKit/src/main/java/danxx/bitmapkit/compress/Compressor.java: -------------------------------------------------------------------------------- 1 | package danxx.bitmapkit.compress; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | 6 | import java.io.File; 7 | import java.io.IOException; 8 | import java.util.concurrent.Callable; 9 | 10 | import io.reactivex.Flowable; 11 | 12 | /** 13 | * Created by danxx on 2018/1/13. 14 | */ 15 | 16 | public class Compressor { 17 | 18 | private int maxWidth = 612; 19 | private int maxHeight = 816; 20 | private Bitmap.CompressFormat compressFormat = Bitmap.CompressFormat.JPEG; 21 | private int quality = 80; 22 | private String destinationDirectoryPath; 23 | 24 | public Compressor(Context context) { 25 | destinationDirectoryPath = context.getCacheDir().getPath() + File.separator + "images"; 26 | } 27 | 28 | public Compressor setMaxWidth(int maxWidth) { 29 | this.maxWidth = maxWidth; 30 | return this; 31 | } 32 | 33 | public Compressor setMaxHeight(int maxHeight) { 34 | this.maxHeight = maxHeight; 35 | return this; 36 | } 37 | 38 | public Compressor setCompressFormat(Bitmap.CompressFormat compressFormat) { 39 | this.compressFormat = compressFormat; 40 | return this; 41 | } 42 | 43 | public Compressor setQuality(int quality) { 44 | this.quality = quality; 45 | return this; 46 | } 47 | 48 | public Compressor setDestinationDirectoryPath(String destinationDirectoryPath) { 49 | this.destinationDirectoryPath = destinationDirectoryPath; 50 | return this; 51 | } 52 | 53 | public File compressToFile(File imageFile) throws IOException { 54 | return compressToFile(imageFile, imageFile.getName()); 55 | } 56 | 57 | public File compressToFile(File imageFile, String compressedFileName) throws IOException { 58 | return CompressUtil.compressImage(imageFile, maxWidth, maxHeight, compressFormat, quality, 59 | destinationDirectoryPath + File.separator + compressedFileName); 60 | } 61 | 62 | public Bitmap compressToBitmap(File imageFile) throws IOException { 63 | return CompressUtil.decodeSampledBitmapFromFile(imageFile, maxWidth, maxHeight); 64 | } 65 | 66 | public Flowable compressToFileAsFlowable(final File imageFile) { 67 | return compressToFileAsFlowable(imageFile, imageFile.getName()); 68 | } 69 | 70 | public Flowable compressToFileAsFlowable(final File imageFile, final String compressedFileName) { 71 | return Flowable.defer(new Callable>() { 72 | @Override 73 | public Flowable call() { 74 | try { 75 | return Flowable.just(compressToFile(imageFile, compressedFileName)); 76 | } catch (IOException e) { 77 | return Flowable.error(e); 78 | } 79 | } 80 | }); 81 | } 82 | 83 | public Flowable compressToBitmapAsFlowable(final File imageFile) { 84 | return Flowable.defer(new Callable>() { 85 | @Override 86 | public Flowable call() { 87 | try { 88 | return Flowable.just(compressToBitmap(imageFile)); 89 | } catch (IOException e) { 90 | return Flowable.error(e); 91 | } 92 | } 93 | }); 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /Lib_BitmapKit/src/main/java/danxx/bitmapkit/crop/BitmapCropUtil.java: -------------------------------------------------------------------------------- 1 | package danxx.bitmapkit.crop; 2 | 3 | import android.graphics.Bitmap; 4 | import android.util.Log; 5 | 6 | import com.glidebitmappool.GlideBitmapPool; 7 | import com.glidebitmappool.internal.BitmapPool; 8 | 9 | import hugo.weaving.DebugLog; 10 | 11 | /** 12 | * Created by danxx on 2018/1/16. 13 | * Bitmap裁剪工具 14 | */ 15 | 16 | public class BitmapCropUtil { 17 | 18 | /** 19 | * 默认回收原图的裁剪 20 | * @param srcBitmap 21 | * @param needHeight 22 | * @return 23 | */ 24 | public static Bitmap cropBitmapBottom(Bitmap srcBitmap, int needHeight){ 25 | return cropBitmapBottom(srcBitmap,needHeight,true); 26 | } 27 | /** 28 | * 裁剪一定高度保留下面 29 | * @param srcBitmap 30 | * @param needHeight 31 | * @param recycleSrc 是否回收原图 32 | * @return 33 | */ 34 | @DebugLog 35 | public static Bitmap cropBitmapBottom(Bitmap srcBitmap, int needHeight, boolean recycleSrc) { 36 | 37 | Log.d("danxx", "cropBitmapBottom before h : "+srcBitmap.getHeight()); 38 | 39 | /**裁剪保留下部分的第一个像素的Y坐标*/ 40 | int needY = srcBitmap.getHeight() - needHeight; 41 | 42 | /**裁剪关键步骤*/ 43 | Bitmap cropBitmap = Bitmap.createBitmap(srcBitmap,0,needY,srcBitmap.getWidth(),needHeight); 44 | 45 | Log.d("danxx", "cropBitmapBottom after h : "+cropBitmap.getHeight()); 46 | 47 | /**回收之前的Bitmap*/ 48 | if (recycleSrc && srcBitmap != null && !srcBitmap.equals(cropBitmap) && !srcBitmap.isRecycled()) { 49 | GlideBitmapPool.putBitmap(srcBitmap); 50 | } 51 | 52 | return cropBitmap; 53 | } 54 | 55 | 56 | /** 57 | * 默认回收原图的裁剪 58 | * @param srcBitmap 59 | * @param needHeight 60 | * @return 61 | */ 62 | public static Bitmap cropBitmapTop(Bitmap srcBitmap, int needHeight) { 63 | return cropBitmapTop(srcBitmap, needHeight, true); 64 | } 65 | /** 66 | * 裁剪一定高度保留下面 67 | * @param srcBitmap 68 | * @param needHeight 69 | * @param recycleSrc 是否回收原图 70 | * @return 71 | */ 72 | @DebugLog 73 | public static Bitmap cropBitmapTop(Bitmap srcBitmap, int needHeight, boolean recycleSrc) { 74 | 75 | Log.d("danxx", "cropBitmapBottom before h : "+srcBitmap.getHeight()); 76 | 77 | /**裁剪保留上部分的第一个像素的Y坐标*/ 78 | int needY = 0; 79 | 80 | /**裁剪关键步骤*/ 81 | Bitmap cropBitmap = Bitmap.createBitmap(srcBitmap,0,needY,srcBitmap.getWidth(),needHeight); 82 | 83 | Log.d("danxx", "cropBitmapBottom after h : "+cropBitmap.getHeight()); 84 | 85 | /**回收之前的Bitmap*/ 86 | if (recycleSrc && srcBitmap != null && !srcBitmap.equals(cropBitmap) && !srcBitmap.isRecycled()) { 87 | GlideBitmapPool.putBitmap(srcBitmap); 88 | } 89 | 90 | return cropBitmap; 91 | } 92 | 93 | 94 | public static Bitmap cropBitmapLeft(Bitmap srcBitmap, int needWidth) { 95 | return cropBitmapLeft(srcBitmap,needWidth,true); 96 | } 97 | /** 98 | * 裁剪一定高度保留左边 99 | * @param srcBitmap 100 | * @param needWidth 101 | * @return 102 | */ 103 | @DebugLog 104 | public static Bitmap cropBitmapLeft(Bitmap srcBitmap, int needWidth, boolean recycleSrc) { 105 | 106 | Log.d("danxx", "cropBitmapLeft before w : "+srcBitmap.getWidth()); 107 | 108 | /**裁剪关键步骤*/ 109 | Bitmap cropBitmap = Bitmap.createBitmap(srcBitmap, 0, 0, needWidth, srcBitmap.getHeight()); 110 | 111 | Log.d("danxx", "cropBitmapLeft after w : "+cropBitmap.getWidth()); 112 | 113 | /**回收之前的Bitmap*/ 114 | if (recycleSrc && srcBitmap != null && !srcBitmap.equals(cropBitmap) && !srcBitmap.isRecycled()) { 115 | GlideBitmapPool.putBitmap(srcBitmap); 116 | } 117 | 118 | return cropBitmap; 119 | } 120 | 121 | 122 | public static Bitmap cropBitmapRight(Bitmap srcBitmap, int needWidth) { 123 | return cropBitmapRight(srcBitmap, needWidth, true); 124 | } 125 | /** 126 | * 裁剪一定高度保留左边 127 | * @param srcBitmap 128 | * @param needWidth 129 | * @return 130 | */ 131 | @DebugLog 132 | public static Bitmap cropBitmapRight(Bitmap srcBitmap, int needWidth, boolean recycleSrc) { 133 | 134 | Log.d("danxx", "cropBitmapRight before w : "+srcBitmap.getWidth()); 135 | 136 | int needX = srcBitmap.getWidth() - needWidth; 137 | 138 | /**裁剪关键步骤*/ 139 | Bitmap cropBitmap = Bitmap.createBitmap(srcBitmap, needX, 0, needWidth, srcBitmap.getHeight()); 140 | 141 | Log.d("danxx", "cropBitmapRight after w : "+cropBitmap.getWidth()); 142 | 143 | /**回收之前的Bitmap*/ 144 | if (recycleSrc && srcBitmap != null && !srcBitmap.equals(cropBitmap) && !srcBitmap.isRecycled()) { 145 | GlideBitmapPool.putBitmap(srcBitmap); 146 | } 147 | 148 | return cropBitmap; 149 | } 150 | 151 | /** 152 | * 默认回收 随意裁剪 153 | * @param srcBitmap 154 | * @param firstPixelX 155 | * @param firstPixelY 156 | * @param needWidth 157 | * @param needHeight 158 | * @return 159 | */ 160 | public static Bitmap cropBitmapCustom(Bitmap srcBitmap, int firstPixelX, int firstPixelY, int needWidth, int needHeight) { 161 | return cropBitmapCustom(srcBitmap, firstPixelX, firstPixelY, needWidth, needHeight, true); 162 | } 163 | 164 | /** 165 | * 自定义裁剪,根据第一个像素点(左上角)X和Y轴坐标和需要的宽高来裁剪 166 | * @param srcBitmap 167 | * @param firstPixelX 168 | * @param firstPixelY 169 | * @param needWidth 170 | * @param needHeight 171 | * @param recycleSrc 172 | * @return 173 | */ 174 | @DebugLog 175 | public static Bitmap cropBitmapCustom(Bitmap srcBitmap, int firstPixelX, int firstPixelY, int needWidth, int needHeight, boolean recycleSrc) { 176 | 177 | Log.d("danxx", "cropBitmapRight before w : "+srcBitmap.getWidth()); 178 | Log.d("danxx", "cropBitmapRight before h : "+srcBitmap.getHeight()); 179 | 180 | if(firstPixelX + needWidth > srcBitmap.getWidth()){ 181 | needWidth = srcBitmap.getWidth() - firstPixelX; 182 | } 183 | 184 | if(firstPixelY + needHeight > srcBitmap.getHeight()){ 185 | needHeight = srcBitmap.getHeight() - firstPixelY; 186 | } 187 | 188 | /**裁剪关键步骤*/ 189 | Bitmap cropBitmap = Bitmap.createBitmap(srcBitmap, firstPixelX, firstPixelY, needWidth, needHeight); 190 | 191 | Log.d("danxx", "cropBitmapRight after w : "+cropBitmap.getWidth()); 192 | Log.d("danxx", "cropBitmapRight after h : "+cropBitmap.getHeight()); 193 | 194 | 195 | /**回收之前的Bitmap*/ 196 | if (recycleSrc && srcBitmap != null && !srcBitmap.equals(cropBitmap) && !srcBitmap.isRecycled()) { 197 | GlideBitmapPool.putBitmap(srcBitmap); 198 | } 199 | 200 | return cropBitmap; 201 | } 202 | 203 | } 204 | -------------------------------------------------------------------------------- /Lib_BitmapKit/src/main/java/danxx/bitmapkit/draw/BitmapDrawUtil.java: -------------------------------------------------------------------------------- 1 | package danxx.bitmapkit.draw; 2 | 3 | /** 4 | * Created by danxx on 2018/1/27. 5 | * @Desc Bitmap绘制工具 6 | */ 7 | 8 | public class BitmapDrawUtil { 9 | 10 | 11 | 12 | } 13 | -------------------------------------------------------------------------------- /Lib_BitmapKit/src/main/java/danxx/bitmapkit/rotate/BitmapRotateUtil.java: -------------------------------------------------------------------------------- 1 | package danxx.bitmapkit.rotate; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.Matrix; 5 | 6 | import com.glidebitmappool.GlideBitmapPool; 7 | 8 | /** 9 | * Created by danxx on 2018/1/28. 10 | * @Desc Bitmap旋转工具 11 | */ 12 | 13 | public class BitmapRotateUtil { 14 | 15 | /** 16 | * 默认回收 17 | * @param srcBitmap 18 | * @param degrees 19 | * @return 20 | */ 21 | public static Bitmap rotateBitmap(Bitmap srcBitmap, float degrees) { 22 | return rotateBitmap(srcBitmap, degrees, true); 23 | } 24 | 25 | /** 26 | * 选择变换 27 | * 28 | * @param srcBitmap 原图 29 | * @param degrees 旋转角度,可正可负 30 | * @return 旋转后的图片 31 | */ 32 | public static Bitmap rotateBitmap(Bitmap srcBitmap, float degrees, boolean recycleSrc) { 33 | if (srcBitmap == null) { 34 | return null; 35 | } 36 | int width = srcBitmap.getWidth(); 37 | int height = srcBitmap.getHeight(); 38 | Matrix matrix = new Matrix(); 39 | matrix.setRotate(degrees); 40 | // 围绕原地进行旋转 41 | Bitmap newBM = Bitmap.createBitmap(srcBitmap, 0, 0, width, height, matrix, true); 42 | if (recycleSrc && srcBitmap != null & !srcBitmap.isRecycled() && !srcBitmap.equals(newBM)) { 43 | GlideBitmapPool.putBitmap(srcBitmap); 44 | } 45 | return newBM; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /Lib_BitmapKit/src/main/java/danxx/bitmapkit/scale/BitmapScaleUtil.java: -------------------------------------------------------------------------------- 1 | package danxx.bitmapkit.scale; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.BitmapFactory; 5 | import android.graphics.Matrix; 6 | 7 | import com.glidebitmappool.GlideBitmapPool; 8 | import com.glidebitmappool.internal.BitmapPool; 9 | 10 | /** 11 | * Created by danxx on 2018/1/16. 12 | * Bitmap缩放工具 13 | */ 14 | 15 | public class BitmapScaleUtil { 16 | 17 | /** 18 | * 默认回收原图 19 | * @param srcBitmap 20 | * @param newWidth 21 | * @param newHeight 22 | * @return 23 | */ 24 | public static Bitmap scaleBitmap(Bitmap srcBitmap, int newWidth, int newHeight) { 25 | return scaleBitmap(srcBitmap, newWidth, newHeight, true); 26 | } 27 | /** 28 | * 按新的宽高缩放图片 29 | * 30 | * @param srcBitmap 原图 31 | * @param newWidth 新的需要的宽度 32 | * @param newHeight 新的需要的高度 33 | * @param recycleSrc 是否回收原图 34 | * @return 35 | */ 36 | public static Bitmap scaleBitmap(Bitmap srcBitmap, int newWidth, int newHeight, boolean recycleSrc) { 37 | if (srcBitmap == null) { 38 | return null; 39 | } 40 | int width = srcBitmap.getWidth(); 41 | int height = srcBitmap.getHeight(); 42 | float scaleWidth = ((float) newWidth) / width; 43 | float scaleHeight = ((float) newHeight) / height; 44 | Matrix matrix = new Matrix(); 45 | matrix.postScale(scaleWidth, scaleHeight); 46 | Bitmap newbm = Bitmap.createBitmap(srcBitmap, 0, 0, width, height, matrix,true); 47 | if (recycleSrc && srcBitmap != null & !srcBitmap.isRecycled() && !srcBitmap.equals(newbm)) { 48 | GlideBitmapPool.putBitmap(srcBitmap); 49 | } 50 | return newbm; 51 | } 52 | 53 | /** 54 | * 默认回收原图 55 | * @param srcBitmap 56 | * @param scaleWidth 57 | * @param scaleHeight 58 | * @return 59 | */ 60 | public static Bitmap scaleBitmap(Bitmap srcBitmap, float scaleWidth, float scaleHeight) { 61 | return scaleBitmap(srcBitmap, scaleWidth, scaleHeight, true); 62 | } 63 | /** 64 | * 根据指定的宽度比例值和高度比例值进行缩放 65 | * @param srcBitmap 66 | * @param scaleWidth 67 | * @param scaleHeight 68 | * @param recycleSrc 是否回收Bitmap 69 | * @return 70 | */ 71 | public static Bitmap scaleBitmap(Bitmap srcBitmap, float scaleWidth, float scaleHeight, boolean recycleSrc) { 72 | int width = srcBitmap.getWidth(); 73 | int height = srcBitmap.getHeight(); 74 | Matrix matrix = new Matrix(); 75 | matrix.postScale(scaleWidth, scaleHeight); 76 | Bitmap bitmap = Bitmap.createBitmap(srcBitmap, 0, 0, width, height, matrix, true); 77 | if (bitmap != null) { 78 | /**回收*/ 79 | if (recycleSrc && srcBitmap != null && !srcBitmap.equals(bitmap) && !srcBitmap.isRecycled()) { 80 | GlideBitmapPool.putBitmap(srcBitmap); 81 | } 82 | return bitmap; 83 | } else { 84 | return srcBitmap; 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Lib_BitmapKit/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | BitmapKit 3 | 4 | -------------------------------------------------------------------------------- /Lib_BitmapKit/src/test/java/danxx/bitmapkit/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package danxx.bitmapkit; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /Lib_ImageLoader/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /Lib_ImageLoader/README.md: -------------------------------------------------------------------------------- 1 | # Android DxIamgeLoader Lib 2 | 3 | ## 详细解读博客 4 | http://blog.csdn.net/u010072711/article/details/74502822 5 | 6 | ## 文章导读 7 | 8 | Android DiskLruCache完全解析,硬盘缓存的最佳方案 http://blog.csdn.net/guolin_blog/article/details/28863651 9 | 10 | Android性能优化之使用线程池处理异步任务 http://blog.csdn.net/u010687392/article/details/49850803 11 | 12 | Android开发之高效加载Bitmap http://www.cnblogs.com/absfree/p/5361167.html 13 | 14 | Android线程同步 http://blog.csdn.net/peng6662001/article/details/7277851/ 15 | 16 | ## 方法调用顺序 17 | 18 | load --> placeholder --> error --> into 19 | 20 | 21 | ## 图片加载顺序 22 | 23 | loadFromMemoryCache 成功返回bitmap 24 | 25 | loadFromDisk =》addToMemoryCache 获取成功并压缩后把bitmap添加到运行内存 返回bitmap 26 | 27 | ↑ 28 | 29 | <-------------------------------- 30 | 31 | ↑ 32 | 33 | loadFromNet =》 addToDisk =》 loadFromDisk 网络获取成功后调用loadFromDisk添加到文件缓存(返回压缩的bitmap 并添加到运行内存) 34 | 35 | ![Alt text](./img/flow.png) 36 | 37 | ## Sample: 38 | ``` java 39 | //Application中初始化 40 | public class App extends Application { 41 | @Override 42 | public void onCreate() { 43 | super.onCreate(); 44 | //初始化图片加载库 45 | DxImageLoader.getInstance().init(getApplicationContext()); 46 | } 47 | } 48 | 49 | //activity中调用 50 | /****调用示例***/ 51 | ImageView imageView0 = (ImageView) findViewById(R.id.image0); 52 | DxImageLoader.getInstance() 53 | .load(imgUrls[0]) //load图片地址 54 | .placeholder(R.drawable.default_pic_loading) //placeholder占位图 55 | .error(R.drawable.app_bg) //error错误图 56 | .into(imageView0); //into显示图片的imageView 57 | ``` 58 | 59 | ## 效果图 60 | ![Alt text](./img/dximageloadimg.png) 61 | -------------------------------------------------------------------------------- /Lib_ImageLoader/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | 5 | compileSdkVersion 26 6 | 7 | defaultConfig { 8 | minSdkVersion 15 9 | targetSdkVersion 26 10 | versionCode 1 11 | versionName "1.0" 12 | 13 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 14 | 15 | } 16 | buildTypes { 17 | release { 18 | minifyEnabled false 19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 20 | } 21 | } 22 | } 23 | 24 | dependencies { 25 | compile fileTree(dir: 'libs', include: ['*.jar']) 26 | // androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 27 | // exclude group: 'com.android.support', module: 'support-annotations' 28 | // }) 29 | 30 | // compile 'com.android.support:appcompat-v7:25.4.0' 31 | // testCompile 'junit:junit:4.12' 32 | } 33 | -------------------------------------------------------------------------------- /Lib_ImageLoader/img/dximageloadimg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dawish/BitmapKit/08df654085dd32faccb5e63ce2368cc6b9d56662/Lib_ImageLoader/img/dximageloadimg.png -------------------------------------------------------------------------------- /Lib_ImageLoader/img/flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dawish/BitmapKit/08df654085dd32faccb5e63ce2368cc6b9d56662/Lib_ImageLoader/img/flow.png -------------------------------------------------------------------------------- /Lib_ImageLoader/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in D:\Android_Develop\AndroidSdk/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 | -------------------------------------------------------------------------------- /Lib_ImageLoader/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Lib_ImageLoader/src/main/java/com/dximageloader/DiskLruCache.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 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.dximageloader; 18 | 19 | import java.io.BufferedInputStream; 20 | import java.io.BufferedWriter; 21 | import java.io.Closeable; 22 | import java.io.EOFException; 23 | import java.io.File; 24 | import java.io.FileInputStream; 25 | import java.io.FileNotFoundException; 26 | import java.io.FileOutputStream; 27 | import java.io.FileWriter; 28 | import java.io.FilterOutputStream; 29 | import java.io.IOException; 30 | import java.io.InputStream; 31 | import java.io.InputStreamReader; 32 | import java.io.OutputStream; 33 | import java.io.OutputStreamWriter; 34 | import java.io.Reader; 35 | import java.io.StringWriter; 36 | import java.io.Writer; 37 | import java.lang.reflect.Array; 38 | import java.nio.charset.Charset; 39 | import java.util.ArrayList; 40 | import java.util.Arrays; 41 | import java.util.Iterator; 42 | import java.util.LinkedHashMap; 43 | import java.util.Map; 44 | import java.util.concurrent.Callable; 45 | import java.util.concurrent.ExecutorService; 46 | import java.util.concurrent.LinkedBlockingQueue; 47 | import java.util.concurrent.ThreadPoolExecutor; 48 | import java.util.concurrent.TimeUnit; 49 | 50 | /** 51 | ****************************************************************************** 52 | * Taken from the JB source code, can be found in: 53 | * libcore/luni/src/main/java/libcore/io/DiskLruCache.java 54 | * or direct link: 55 | * https://android.googlesource.com/platform/libcore/+/android-4.1.1_r1/luni/src/main/java/libcore/io/DiskLruCache.java 56 | ****************************************************************************** 57 | * 58 | * A cache that uses a bounded amount of space on a filesystem. Each cache 59 | * entry has a string key and a fixed number of values. Values are byte 60 | * sequences, accessible as streams or files. Each value must be between {@code 61 | * 0} and {@code Integer.MAX_VALUE} bytes in length. 62 | * 63 | *

The cache stores its data in a directory on the filesystem. This 64 | * directory must be exclusive to the cache; the cache may delete or overwrite 65 | * files from its directory. It is an error for multiple processes to use the 66 | * same cache directory at the same time. 67 | * 68 | *

This cache limits the number of bytes that it will store on the 69 | * filesystem. When the number of stored bytes exceeds the limit, the cache will 70 | * remove entries in the background until the limit is satisfied. The limit is 71 | * not strict: the cache may temporarily exceed it while waiting for files to be 72 | * deleted. The limit does not include filesystem overhead or the cache 73 | * journal so space-sensitive applications should set a conservative limit. 74 | * 75 | *

Clients call {@link #edit} to create or update the values of an entry. An 76 | * entry may have only one editor at one time; if a value is not available to be 77 | * edited then {@link #edit} will return null. 78 | *

    79 | *
  • When an entry is being created it is necessary to 80 | * supply a full set of values; the empty value should be used as a 81 | * placeholder if necessary. 82 | *
  • When an entry is being edited, it is not necessary 83 | * to supply data for every value; values default to their previous 84 | * value. 85 | *
86 | * Every {@link #edit} call must be matched by a call to {@link Editor#commit} 87 | * or {@link Editor#abort}. Committing is atomic: a read observes the full set 88 | * of values as they were before or after the commit, but never a mix of values. 89 | * 90 | *

Clients call {@link #get} to read a snapshot of an entry. The read will 91 | * observe the value at the time that {@link #get} was called. Updates and 92 | * removals after the call do not impact ongoing reads. 93 | * 94 | *

This class is tolerant of some I/O errors. If files are missing from the 95 | * filesystem, the corresponding entries will be dropped from the cache. If 96 | * an error occurs while writing a cache value, the edit will fail silently. 97 | * Callers should handle other problems by catching {@code IOException} and 98 | * responding appropriately. 99 | */ 100 | public final class DiskLruCache implements Closeable { 101 | static final String JOURNAL_FILE = "journal"; 102 | static final String JOURNAL_FILE_TMP = "journal.tmp"; 103 | static final String MAGIC = "libcore.io.DiskLruCache"; 104 | static final String VERSION_1 = "1"; 105 | static final long ANY_SEQUENCE_NUMBER = -1; 106 | private static final String CLEAN = "CLEAN"; 107 | private static final String DIRTY = "DIRTY"; 108 | private static final String REMOVE = "REMOVE"; 109 | private static final String READ = "READ"; 110 | 111 | private static final Charset UTF_8 = Charset.forName("UTF-8"); 112 | private static final int IO_BUFFER_SIZE = 8 * 1024; 113 | 114 | /* 115 | * This cache uses a journal file named "journal". A typical journal file 116 | * looks like this: 117 | * libcore.io.DiskLruCache 118 | * 1 119 | * 100 120 | * 2 121 | * 122 | * CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054 123 | * DIRTY 335c4c6028171cfddfbaae1a9c313c52 124 | * CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342 125 | * REMOVE 335c4c6028171cfddfbaae1a9c313c52 126 | * DIRTY 1ab96a171faeeee38496d8b330771a7a 127 | * CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234 128 | * READ 335c4c6028171cfddfbaae1a9c313c52 129 | * READ 3400330d1dfc7f3f7f4b8d4d803dfcf6 130 | * 131 | * The first five lines of the journal form its header. They are the 132 | * constant string "libcore.io.DiskLruCache", the disk cache's version, 133 | * the application's version, the value count, and a blank line. 134 | * 135 | * Each of the subsequent lines in the file is a record of the state of a 136 | * cache entry. Each line contains space-separated values: a state, a key, 137 | * and optional state-specific values. 138 | * o DIRTY lines track that an entry is actively being created or updated. 139 | * Every successful DIRTY action should be followed by a CLEAN or REMOVE 140 | * action. DIRTY lines without a matching CLEAN or REMOVE indicate that 141 | * temporary files may need to be deleted. 142 | * o CLEAN lines track a cache entry that has been successfully published 143 | * and may be read. A publish line is followed by the lengths of each of 144 | * its values. 145 | * o READ lines track accesses for LRU. 146 | * o REMOVE lines track entries that have been deleted. 147 | * 148 | * The journal file is appended to as cache operations occur. The journal may 149 | * occasionally be compacted by dropping redundant lines. A temporary file named 150 | * "journal.tmp" will be used during compaction; that file should be deleted if 151 | * it exists when the cache is opened. 152 | */ 153 | 154 | private final File directory; 155 | private final File journalFile; 156 | private final File journalFileTmp; 157 | private final int appVersion; 158 | private final long maxSize; 159 | private final int valueCount; 160 | private long size = 0; 161 | private Writer journalWriter; 162 | private final LinkedHashMap lruEntries 163 | = new LinkedHashMap(0, 0.75f, true); 164 | private int redundantOpCount; 165 | 166 | /** 167 | * To differentiate between old and current snapshots, each entry is given 168 | * a sequence number each time an edit is committed. A snapshot is stale if 169 | * its sequence number is not equal to its entry's sequence number. 170 | */ 171 | private long nextSequenceNumber = 0; 172 | 173 | /* From java.util.Arrays */ 174 | @SuppressWarnings("unchecked") 175 | private static T[] copyOfRange(T[] original, int start, int end) { 176 | final int originalLength = original.length; // For exception priority compatibility. 177 | if (start > end) { 178 | throw new IllegalArgumentException(); 179 | } 180 | if (start < 0 || start > originalLength) { 181 | throw new ArrayIndexOutOfBoundsException(); 182 | } 183 | final int resultLength = end - start; 184 | final int copyLength = Math.min(resultLength, originalLength - start); 185 | final T[] result = (T[]) Array 186 | .newInstance(original.getClass().getComponentType(), resultLength); 187 | System.arraycopy(original, start, result, 0, copyLength); 188 | return result; 189 | } 190 | 191 | /** 192 | * Returns the remainder of 'reader' as a string, closing it when done. 193 | */ 194 | public static String readFully(Reader reader) throws IOException { 195 | try { 196 | StringWriter writer = new StringWriter(); 197 | char[] buffer = new char[1024]; 198 | int count; 199 | while ((count = reader.read(buffer)) != -1) { 200 | writer.write(buffer, 0, count); 201 | } 202 | return writer.toString(); 203 | } finally { 204 | reader.close(); 205 | } 206 | } 207 | 208 | /** 209 | * Returns the ASCII characters up to but not including the next "\r\n", or 210 | * "\n". 211 | * 212 | * @throws EOFException if the stream is exhausted before the next newline 213 | * character. 214 | */ 215 | public static String readAsciiLine(InputStream in) throws IOException { 216 | // TODO: support UTF-8 here instead 217 | 218 | StringBuilder result = new StringBuilder(80); 219 | while (true) { 220 | int c = in.read(); 221 | if (c == -1) { 222 | throw new EOFException(); 223 | } else if (c == '\n') { 224 | break; 225 | } 226 | 227 | result.append((char) c); 228 | } 229 | int length = result.length(); 230 | if (length > 0 && result.charAt(length - 1) == '\r') { 231 | result.setLength(length - 1); 232 | } 233 | return result.toString(); 234 | } 235 | 236 | /** 237 | * Closes 'closeable', ignoring any checked exceptions. Does nothing if 'closeable' is null. 238 | */ 239 | public static void closeQuietly(Closeable closeable) { 240 | if (closeable != null) { 241 | try { 242 | closeable.close(); 243 | } catch (RuntimeException rethrown) { 244 | throw rethrown; 245 | } catch (Exception ignored) { 246 | } 247 | } 248 | } 249 | 250 | /** 251 | * Recursively delete everything in {@code dir}. 252 | */ 253 | // TODO: this should specify paths as Strings rather than as Files 254 | public static void deleteContents(File dir) throws IOException { 255 | File[] files = dir.listFiles(); 256 | if (files == null) { 257 | throw new IllegalArgumentException("not a directory: " + dir); 258 | } 259 | for (File file : files) { 260 | if (file.isDirectory()) { 261 | deleteContents(file); 262 | } 263 | if (!file.delete()) { 264 | throw new IOException("failed to delete file: " + file); 265 | } 266 | } 267 | } 268 | 269 | /** This cache uses a single background thread to evict entries. */ 270 | private final ExecutorService executorService = new ThreadPoolExecutor(0, 1, 271 | 60L, TimeUnit.SECONDS, new LinkedBlockingQueue()); 272 | private final Callable cleanupCallable = new Callable() { 273 | @Override public Void call() throws Exception { 274 | synchronized (DiskLruCache.this) { 275 | if (journalWriter == null) { 276 | return null; // closed 277 | } 278 | trimToSize(); 279 | if (journalRebuildRequired()) { 280 | rebuildJournal(); 281 | redundantOpCount = 0; 282 | } 283 | } 284 | return null; 285 | } 286 | }; 287 | 288 | private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) { 289 | this.directory = directory; 290 | this.appVersion = appVersion; 291 | this.journalFile = new File(directory, JOURNAL_FILE); 292 | this.journalFileTmp = new File(directory, JOURNAL_FILE_TMP); 293 | this.valueCount = valueCount; 294 | this.maxSize = maxSize; 295 | } 296 | 297 | /** 298 | * Opens the cache in {@code directory}, creating a cache if none exists 299 | * there. 300 | * 301 | * @param directory a writable directory 302 | * @param appVersion 303 | * @param valueCount the number of values per cache entry. Must be positive. 304 | * @param maxSize the maximum number of bytes this cache should use to store 305 | * @throws IOException if reading or writing the cache directory fails 306 | */ 307 | public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize) 308 | throws IOException { 309 | if (maxSize <= 0) { 310 | throw new IllegalArgumentException("maxSize <= 0"); 311 | } 312 | if (valueCount <= 0) { 313 | throw new IllegalArgumentException("valueCount <= 0"); 314 | } 315 | 316 | // prefer to pick up where we left off 317 | DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize); 318 | if (cache.journalFile.exists()) { 319 | try { 320 | cache.readJournal(); 321 | cache.processJournal(); 322 | cache.journalWriter = new BufferedWriter(new FileWriter(cache.journalFile, true), 323 | IO_BUFFER_SIZE); 324 | return cache; 325 | } catch (IOException journalIsCorrupt) { 326 | // System.logW("DiskLruCache " + directory + " is corrupt: " 327 | // + journalIsCorrupt.getMessage() + ", removing"); 328 | cache.delete(); 329 | } 330 | } 331 | 332 | // create a new empty cache 333 | directory.mkdirs(); 334 | cache = new DiskLruCache(directory, appVersion, valueCount, maxSize); 335 | cache.rebuildJournal(); 336 | return cache; 337 | } 338 | 339 | private void readJournal() throws IOException { 340 | InputStream in = new BufferedInputStream(new FileInputStream(journalFile), IO_BUFFER_SIZE); 341 | try { 342 | String magic = readAsciiLine(in); 343 | String version = readAsciiLine(in); 344 | String appVersionString = readAsciiLine(in); 345 | String valueCountString = readAsciiLine(in); 346 | String blank = readAsciiLine(in); 347 | if (!MAGIC.equals(magic) 348 | || !VERSION_1.equals(version) 349 | || !Integer.toString(appVersion).equals(appVersionString) 350 | || !Integer.toString(valueCount).equals(valueCountString) 351 | || !"".equals(blank)) { 352 | throw new IOException("unexpected journal header: [" 353 | + magic + ", " + version + ", " + valueCountString + ", " + blank + "]"); 354 | } 355 | 356 | while (true) { 357 | try { 358 | readJournalLine(readAsciiLine(in)); 359 | } catch (EOFException endOfJournal) { 360 | break; 361 | } 362 | } 363 | } finally { 364 | closeQuietly(in); 365 | } 366 | } 367 | 368 | private void readJournalLine(String line) throws IOException { 369 | String[] parts = line.split(" "); 370 | if (parts.length < 2) { 371 | throw new IOException("unexpected journal line: " + line); 372 | } 373 | 374 | String key = parts[1]; 375 | if (parts[0].equals(REMOVE) && parts.length == 2) { 376 | lruEntries.remove(key); 377 | return; 378 | } 379 | 380 | Entry entry = lruEntries.get(key); 381 | if (entry == null) { 382 | entry = new Entry(key); 383 | lruEntries.put(key, entry); 384 | } 385 | 386 | if (parts[0].equals(CLEAN) && parts.length == 2 + valueCount) { 387 | entry.readable = true; 388 | entry.currentEditor = null; 389 | entry.setLengths(copyOfRange(parts, 2, parts.length)); 390 | } else if (parts[0].equals(DIRTY) && parts.length == 2) { 391 | entry.currentEditor = new Editor(entry); 392 | } else if (parts[0].equals(READ) && parts.length == 2) { 393 | // this work was already done by calling lruEntries.get() 394 | } else { 395 | throw new IOException("unexpected journal line: " + line); 396 | } 397 | } 398 | 399 | /** 400 | * Computes the initial size and collects garbage as a part of opening the 401 | * cache. Dirty entries are assumed to be inconsistent and will be deleted. 402 | */ 403 | private void processJournal() throws IOException { 404 | deleteIfExists(journalFileTmp); 405 | for (Iterator i = lruEntries.values().iterator(); i.hasNext(); ) { 406 | Entry entry = i.next(); 407 | if (entry.currentEditor == null) { 408 | for (int t = 0; t < valueCount; t++) { 409 | size += entry.lengths[t]; 410 | } 411 | } else { 412 | entry.currentEditor = null; 413 | for (int t = 0; t < valueCount; t++) { 414 | deleteIfExists(entry.getCleanFile(t)); 415 | deleteIfExists(entry.getDirtyFile(t)); 416 | } 417 | i.remove(); 418 | } 419 | } 420 | } 421 | 422 | /** 423 | * Creates a new journal that omits redundant information. This replaces the 424 | * current journal if it exists. 425 | */ 426 | private synchronized void rebuildJournal() throws IOException { 427 | if (journalWriter != null) { 428 | journalWriter.close(); 429 | } 430 | 431 | Writer writer = new BufferedWriter(new FileWriter(journalFileTmp), IO_BUFFER_SIZE); 432 | writer.write(MAGIC); 433 | writer.write("\n"); 434 | writer.write(VERSION_1); 435 | writer.write("\n"); 436 | writer.write(Integer.toString(appVersion)); 437 | writer.write("\n"); 438 | writer.write(Integer.toString(valueCount)); 439 | writer.write("\n"); 440 | writer.write("\n"); 441 | 442 | for (Entry entry : lruEntries.values()) { 443 | if (entry.currentEditor != null) { 444 | writer.write(DIRTY + ' ' + entry.key + '\n'); 445 | } else { 446 | writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n'); 447 | } 448 | } 449 | 450 | writer.close(); 451 | journalFileTmp.renameTo(journalFile); 452 | journalWriter = new BufferedWriter(new FileWriter(journalFile, true), IO_BUFFER_SIZE); 453 | } 454 | 455 | private static void deleteIfExists(File file) throws IOException { 456 | // try { 457 | // Libcore.os.remove(file.getPath()); 458 | // } catch (ErrnoException errnoException) { 459 | // if (errnoException.errno != OsConstants.ENOENT) { 460 | // throw errnoException.rethrowAsIOException(); 461 | // } 462 | // } 463 | if (file.exists() && !file.delete()) { 464 | throw new IOException(); 465 | } 466 | } 467 | 468 | /** 469 | * Returns a snapshot of the entry named {@code key}, or null if it doesn't 470 | * exist is not currently readable. If a value is returned, it is moved to 471 | * the head of the LRU queue. 472 | */ 473 | public synchronized Snapshot get(String key) throws IOException { 474 | checkNotClosed(); 475 | validateKey(key); 476 | Entry entry = lruEntries.get(key); 477 | if (entry == null) { 478 | return null; 479 | } 480 | 481 | if (!entry.readable) { 482 | return null; 483 | } 484 | 485 | /* 486 | * Open all streams eagerly to guarantee that we see a single published 487 | * snapshot. If we opened streams lazily then the streams could come 488 | * from different edits. 489 | */ 490 | InputStream[] ins = new InputStream[valueCount]; 491 | try { 492 | for (int i = 0; i < valueCount; i++) { 493 | ins[i] = new FileInputStream(entry.getCleanFile(i)); 494 | } 495 | } catch (FileNotFoundException e) { 496 | // a file must have been deleted manually! 497 | return null; 498 | } 499 | 500 | redundantOpCount++; 501 | journalWriter.append(READ + ' ' + key + '\n'); 502 | if (journalRebuildRequired()) { 503 | executorService.submit(cleanupCallable); 504 | } 505 | 506 | return new Snapshot(key, entry.sequenceNumber, ins); 507 | } 508 | 509 | /** 510 | * Returns an editor for the entry named {@code key}, or null if another 511 | * edit is in progress. 512 | */ 513 | public Editor edit(String key) throws IOException { 514 | return edit(key, ANY_SEQUENCE_NUMBER); 515 | } 516 | 517 | private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException { 518 | checkNotClosed(); 519 | validateKey(key); 520 | Entry entry = lruEntries.get(key); 521 | if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER 522 | && (entry == null || entry.sequenceNumber != expectedSequenceNumber)) { 523 | return null; // snapshot is stale 524 | } 525 | if (entry == null) { 526 | entry = new Entry(key); 527 | lruEntries.put(key, entry); 528 | } else if (entry.currentEditor != null) { 529 | return null; // another edit is in progress 530 | } 531 | 532 | Editor editor = new Editor(entry); 533 | entry.currentEditor = editor; 534 | 535 | // flush the journal before creating files to prevent file leaks 536 | journalWriter.write(DIRTY + ' ' + key + '\n'); 537 | journalWriter.flush(); 538 | return editor; 539 | } 540 | 541 | /** 542 | * Returns the directory where this cache stores its data. 543 | */ 544 | public File getDirectory() { 545 | return directory; 546 | } 547 | 548 | /** 549 | * Returns the maximum number of bytes that this cache should use to store 550 | * its data. 551 | */ 552 | public long maxSize() { 553 | return maxSize; 554 | } 555 | 556 | /** 557 | * Returns the number of bytes currently being used to store the values in 558 | * this cache. This may be greater than the max size if a background 559 | * deletion is pending. 560 | */ 561 | public synchronized long size() { 562 | return size; 563 | } 564 | 565 | private synchronized void completeEdit(Editor editor, boolean success) throws IOException { 566 | Entry entry = editor.entry; 567 | if (entry.currentEditor != editor) { 568 | throw new IllegalStateException(); 569 | } 570 | 571 | // if this edit is creating the entry for the first time, every index must have a value 572 | if (success && !entry.readable) { 573 | for (int i = 0; i < valueCount; i++) { 574 | if (!entry.getDirtyFile(i).exists()) { 575 | editor.abort(); 576 | throw new IllegalStateException("edit didn't create file " + i); 577 | } 578 | } 579 | } 580 | 581 | for (int i = 0; i < valueCount; i++) { 582 | File dirty = entry.getDirtyFile(i); 583 | if (success) { 584 | if (dirty.exists()) { 585 | File clean = entry.getCleanFile(i); 586 | dirty.renameTo(clean); 587 | long oldLength = entry.lengths[i]; 588 | long newLength = clean.length(); 589 | entry.lengths[i] = newLength; 590 | size = size - oldLength + newLength; 591 | } 592 | } else { 593 | deleteIfExists(dirty); 594 | } 595 | } 596 | 597 | redundantOpCount++; 598 | entry.currentEditor = null; 599 | if (entry.readable | success) { 600 | entry.readable = true; 601 | journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n'); 602 | if (success) { 603 | entry.sequenceNumber = nextSequenceNumber++; 604 | } 605 | } else { 606 | lruEntries.remove(entry.key); 607 | journalWriter.write(REMOVE + ' ' + entry.key + '\n'); 608 | } 609 | 610 | if (size > maxSize || journalRebuildRequired()) { 611 | executorService.submit(cleanupCallable); 612 | } 613 | } 614 | 615 | /** 616 | * We only rebuild the journal when it will halve the size of the journal 617 | * and eliminate at least 2000 ops. 618 | */ 619 | private boolean journalRebuildRequired() { 620 | final int REDUNDANT_OP_COMPACT_THRESHOLD = 2000; 621 | return redundantOpCount >= REDUNDANT_OP_COMPACT_THRESHOLD 622 | && redundantOpCount >= lruEntries.size(); 623 | } 624 | 625 | /** 626 | * Drops the entry for {@code key} if it exists and can be removed. Entries 627 | * actively being edited cannot be removed. 628 | * 629 | * @return true if an entry was removed. 630 | */ 631 | public synchronized boolean remove(String key) throws IOException { 632 | checkNotClosed(); 633 | validateKey(key); 634 | Entry entry = lruEntries.get(key); 635 | if (entry == null || entry.currentEditor != null) { 636 | return false; 637 | } 638 | 639 | for (int i = 0; i < valueCount; i++) { 640 | File file = entry.getCleanFile(i); 641 | if (!file.delete()) { 642 | throw new IOException("failed to delete " + file); 643 | } 644 | size -= entry.lengths[i]; 645 | entry.lengths[i] = 0; 646 | } 647 | 648 | redundantOpCount++; 649 | journalWriter.append(REMOVE + ' ' + key + '\n'); 650 | lruEntries.remove(key); 651 | 652 | if (journalRebuildRequired()) { 653 | executorService.submit(cleanupCallable); 654 | } 655 | 656 | return true; 657 | } 658 | 659 | /** 660 | * Returns true if this cache has been closed. 661 | */ 662 | public boolean isClosed() { 663 | return journalWriter == null; 664 | } 665 | 666 | private void checkNotClosed() { 667 | if (journalWriter == null) { 668 | throw new IllegalStateException("cache is closed"); 669 | } 670 | } 671 | 672 | /** 673 | * Force buffered operations to the filesystem. 674 | */ 675 | public synchronized void flush() throws IOException { 676 | checkNotClosed(); 677 | trimToSize(); 678 | journalWriter.flush(); 679 | } 680 | 681 | /** 682 | * Closes this cache. Stored values will remain on the filesystem. 683 | */ 684 | public synchronized void close() throws IOException { 685 | if (journalWriter == null) { 686 | return; // already closed 687 | } 688 | for (Entry entry : new ArrayList(lruEntries.values())) { 689 | if (entry.currentEditor != null) { 690 | entry.currentEditor.abort(); 691 | } 692 | } 693 | trimToSize(); 694 | journalWriter.close(); 695 | journalWriter = null; 696 | } 697 | 698 | private void trimToSize() throws IOException { 699 | while (size > maxSize) { 700 | // Map.Entry toEvict = lruEntries.eldest(); 701 | final Map.Entry toEvict = lruEntries.entrySet().iterator().next(); 702 | remove(toEvict.getKey()); 703 | } 704 | } 705 | 706 | /** 707 | * Closes the cache and deletes all of its stored values. This will delete 708 | * all files in the cache directory including files that weren't created by 709 | * the cache. 710 | */ 711 | public void delete() throws IOException { 712 | close(); 713 | deleteContents(directory); 714 | } 715 | 716 | private void validateKey(String key) { 717 | if (key.contains(" ") || key.contains("\n") || key.contains("\r")) { 718 | throw new IllegalArgumentException( 719 | "keys must not contain spaces or newlines: \"" + key + "\""); 720 | } 721 | } 722 | 723 | private static String inputStreamToString(InputStream in) throws IOException { 724 | return readFully(new InputStreamReader(in, UTF_8)); 725 | } 726 | 727 | /** 728 | * A snapshot of the values for an entry. 729 | */ 730 | public final class Snapshot implements Closeable { 731 | private final String key; 732 | private final long sequenceNumber; 733 | private final InputStream[] ins; 734 | 735 | private Snapshot(String key, long sequenceNumber, InputStream[] ins) { 736 | this.key = key; 737 | this.sequenceNumber = sequenceNumber; 738 | this.ins = ins; 739 | } 740 | 741 | /** 742 | * Returns an editor for this snapshot's entry, or null if either the 743 | * entry has changed since this snapshot was created or if another edit 744 | * is in progress. 745 | */ 746 | public Editor edit() throws IOException { 747 | return DiskLruCache.this.edit(key, sequenceNumber); 748 | } 749 | 750 | /** 751 | * Returns the unbuffered stream with the value for {@code index}. 752 | */ 753 | public InputStream getInputStream(int index) { 754 | return ins[index]; 755 | } 756 | 757 | /** 758 | * Returns the string value for {@code index}. 759 | */ 760 | public String getString(int index) throws IOException { 761 | return inputStreamToString(getInputStream(index)); 762 | } 763 | 764 | @Override public void close() { 765 | for (InputStream in : ins) { 766 | closeQuietly(in); 767 | } 768 | } 769 | } 770 | 771 | /** 772 | * Edits the values for an entry. 773 | */ 774 | public final class Editor { 775 | private final Entry entry; 776 | private boolean hasErrors; 777 | 778 | private Editor(Entry entry) { 779 | this.entry = entry; 780 | } 781 | 782 | /** 783 | * Returns an unbuffered input stream to read the last committed value, 784 | * or null if no value has been committed. 785 | */ 786 | public InputStream newInputStream(int index) throws IOException { 787 | synchronized (DiskLruCache.this) { 788 | if (entry.currentEditor != this) { 789 | throw new IllegalStateException(); 790 | } 791 | if (!entry.readable) { 792 | return null; 793 | } 794 | return new FileInputStream(entry.getCleanFile(index)); 795 | } 796 | } 797 | 798 | /** 799 | * Returns the last committed value as a string, or null if no value 800 | * has been committed. 801 | */ 802 | public String getString(int index) throws IOException { 803 | InputStream in = newInputStream(index); 804 | return in != null ? inputStreamToString(in) : null; 805 | } 806 | 807 | /** 808 | * Returns a new unbuffered output stream to write the value at 809 | * {@code index}. If the underlying output stream encounters errors 810 | * when writing to the filesystem, this edit will be aborted when 811 | * {@link #commit} is called. The returned output stream does not throw 812 | * IOExceptions. 813 | */ 814 | public OutputStream newOutputStream(int index) throws IOException { 815 | synchronized (DiskLruCache.this) { 816 | if (entry.currentEditor != this) { 817 | throw new IllegalStateException(); 818 | } 819 | return new FaultHidingOutputStream(new FileOutputStream(entry.getDirtyFile(index))); 820 | } 821 | } 822 | 823 | /** 824 | * Sets the value at {@code index} to {@code value}. 825 | */ 826 | public void set(int index, String value) throws IOException { 827 | Writer writer = null; 828 | try { 829 | writer = new OutputStreamWriter(newOutputStream(index), UTF_8); 830 | writer.write(value); 831 | } finally { 832 | closeQuietly(writer); 833 | } 834 | } 835 | 836 | /** 837 | * Commits this edit so it is visible to readers. This releases the 838 | * edit lock so another edit may be started on the same key. 839 | */ 840 | public void commit() throws IOException { 841 | if (hasErrors) { 842 | completeEdit(this, false); 843 | remove(entry.key); // the previous entry is stale 844 | } else { 845 | completeEdit(this, true); 846 | } 847 | } 848 | 849 | /** 850 | * Aborts this edit. This releases the edit lock so another edit may be 851 | * started on the same key. 852 | */ 853 | public void abort() throws IOException { 854 | completeEdit(this, false); 855 | } 856 | 857 | private class FaultHidingOutputStream extends FilterOutputStream { 858 | private FaultHidingOutputStream(OutputStream out) { 859 | super(out); 860 | } 861 | 862 | @Override public void write(int oneByte) { 863 | try { 864 | out.write(oneByte); 865 | } catch (IOException e) { 866 | hasErrors = true; 867 | } 868 | } 869 | 870 | @Override public void write(byte[] buffer, int offset, int length) { 871 | try { 872 | out.write(buffer, offset, length); 873 | } catch (IOException e) { 874 | hasErrors = true; 875 | } 876 | } 877 | 878 | @Override public void close() { 879 | try { 880 | out.close(); 881 | } catch (IOException e) { 882 | hasErrors = true; 883 | } 884 | } 885 | 886 | @Override public void flush() { 887 | try { 888 | out.flush(); 889 | } catch (IOException e) { 890 | hasErrors = true; 891 | } 892 | } 893 | } 894 | } 895 | 896 | private final class Entry { 897 | private final String key; 898 | 899 | /** Lengths of this entry's files. */ 900 | private final long[] lengths; 901 | 902 | /** True if this entry has ever been published */ 903 | private boolean readable; 904 | 905 | /** The ongoing edit or null if this entry is not being edited. */ 906 | private Editor currentEditor; 907 | 908 | /** The sequence number of the most recently committed edit to this entry. */ 909 | private long sequenceNumber; 910 | 911 | private Entry(String key) { 912 | this.key = key; 913 | this.lengths = new long[valueCount]; 914 | } 915 | 916 | public String getLengths() throws IOException { 917 | StringBuilder result = new StringBuilder(); 918 | for (long size : lengths) { 919 | result.append(' ').append(size); 920 | } 921 | return result.toString(); 922 | } 923 | 924 | /** 925 | * Set lengths using decimal numbers like "10123". 926 | */ 927 | private void setLengths(String[] strings) throws IOException { 928 | if (strings.length != valueCount) { 929 | throw invalidLengths(strings); 930 | } 931 | 932 | try { 933 | for (int i = 0; i < strings.length; i++) { 934 | lengths[i] = Long.parseLong(strings[i]); 935 | } 936 | } catch (NumberFormatException e) { 937 | throw invalidLengths(strings); 938 | } 939 | } 940 | 941 | private IOException invalidLengths(String[] strings) throws IOException { 942 | throw new IOException("unexpected journal line: " + Arrays.toString(strings)); 943 | } 944 | 945 | public File getCleanFile(int i) { 946 | return new File(directory, key + "." + i); 947 | } 948 | 949 | public File getDirtyFile(int i) { 950 | return new File(directory, key + "." + i + ".tmp"); 951 | } 952 | } 953 | } -------------------------------------------------------------------------------- /Lib_ImageLoader/src/main/java/com/dximageloader/DxImageLoader.java: -------------------------------------------------------------------------------- 1 | package com.dximageloader; 2 | 3 | import android.content.Context; 4 | import android.content.pm.PackageInfo; 5 | import android.content.pm.PackageManager; 6 | import android.graphics.Bitmap; 7 | import android.graphics.BitmapFactory; 8 | import android.os.Environment; 9 | import android.os.Handler; 10 | import android.os.Looper; 11 | import android.util.Log; 12 | import android.util.LruCache; 13 | import android.widget.ImageView; 14 | 15 | import java.io.BufferedInputStream; 16 | import java.io.BufferedOutputStream; 17 | import java.io.Closeable; 18 | import java.io.File; 19 | import java.io.FileDescriptor; 20 | import java.io.FileInputStream; 21 | import java.io.IOException; 22 | import java.io.OutputStream; 23 | import java.net.HttpURLConnection; 24 | import java.net.URL; 25 | import java.security.MessageDigest; 26 | import java.security.NoSuchAlgorithmException; 27 | import java.util.concurrent.ExecutorService; 28 | import java.util.concurrent.LinkedBlockingQueue; 29 | import java.util.concurrent.ThreadPoolExecutor; 30 | import java.util.concurrent.TimeUnit; 31 | 32 | /** 33 | * Created by dawish on 2017/6/25. 34 | */ 35 | public class DxImageLoader { 36 | /** 37 | * 文章导读--------------------------------------------------- 38 | * Android DiskLruCache完全解析,硬盘缓存的最佳方案 http://blog.csdn.net/guolin_blog/article/details/28863651 39 | * Android性能优化之使用线程池处理异步任务 http://blog.csdn.net/u010687392/article/details/49850803 40 | * Android开发之高效加载Bitmap http://www.cnblogs.com/absfree/p/5361167.html 41 | * Android线程同步 http://blog.csdn.net/peng6662001/article/details/7277851/ 42 | * 43 | * 方法调用顺序----------------------------------------------- 44 | * load --> placeholder --> error --> into 45 | * 46 | * 图片加载顺序--------------------------------------------- 47 | * loadFromMemoryCache 成功返回bitmap 48 | * loadFromDisk =》addToMemoryCache 获取成功并压缩后把bitmap添加到运行内存 返回bitmap 49 | * ↑ 50 | * <---------------------------------- 51 | * ↑ 52 | * loadFromNet =》 addToDisk =》 loadFromDisk 网络获取成功后 53 | * 调用loadFromDisk添加到文件缓存(返回压缩的bitmap 并添加到运行内存) 54 | * 55 | * 56 | */ 57 | private final static String TAG = "DxImageLoader"; 58 | /**单例*/ 59 | private static DxImageLoader mInstance; 60 | /**最大的文件缓存量*/ 61 | private static final int MAX_DISK_CACHE_SIZE = 10 * 1024 * 1024; 62 | /**cpu核心数*/ 63 | public static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); 64 | /**线程池线程数*/ 65 | private static final int CORE_POOL_SIZE = CPU_COUNT + 1; 66 | /**最大线程数*/ 67 | private static final int MAX_POOL_SIZE = 2 * CPU_COUNT + 1; 68 | /**线程存活时间(单位:TimeUnit.SECONDS)*/ 69 | private static final long KEEP_ALIVE = 5L; 70 | 71 | /**图片加载线程池*/ 72 | public static final ExecutorService threadPoolExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, 73 | MAX_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, new LinkedBlockingQueue()); 74 | 75 | /**内存缓存*/ 76 | private LruCache mMemoryCache; 77 | /**文件缓存*/ 78 | private DiskLruCache mDiskLruCache; 79 | /**主线的handler方便在其他的线程中显示UI操作*/ 80 | private Handler mMainHandler = new Handler(Looper.getMainLooper()); 81 | 82 | private DxImageLoader(){ 83 | } 84 | private boolean inited = false; 85 | 86 | /**单例模式*/ 87 | public static DxImageLoader getInstance(){ 88 | if(null == mInstance){ 89 | synchronized (DxImageLoader.class){ 90 | if(null == mInstance){ 91 | mInstance = new DxImageLoader(); 92 | } 93 | } 94 | } 95 | return mInstance; 96 | } 97 | 98 | public void init(Context context){ 99 | Log.i(TAG, TAG+" init"); 100 | int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); 101 | int memoryCacheSize = maxMemory / 8; 102 | mMemoryCache = new LruCache(memoryCacheSize){ 103 | @Override 104 | protected int sizeOf(String key, Bitmap value) { 105 | return value.getByteCount() / 1024;// 获取图片占用运行内存大小 106 | } 107 | }; 108 | File diskCacheDir = getDiskCacheDir(context,"dx_cache_images"); 109 | if(!diskCacheDir.exists()){ 110 | diskCacheDir.mkdirs(); 111 | } 112 | if(diskCacheDir.getUsableSpace() >= MAX_DISK_CACHE_SIZE){ //返回分区可用字节的大小 113 | try { 114 | //缓存文件, app版本号, 一个key对于多少个value, 最大缓存空间, 115 | mDiskLruCache = DiskLruCache.open(diskCacheDir, getAppVersion(context), 1, MAX_DISK_CACHE_SIZE); 116 | } catch (IOException e) { 117 | e.printStackTrace(); 118 | } 119 | } 120 | inited = true; 121 | } 122 | 123 | /** 124 | * 如果不存在于内存就add 125 | * @param key 126 | * @param bitmap 127 | */ 128 | private void addToMemoryCache(String key, Bitmap bitmap) { 129 | if (loadFromMemoryCache(key) == null) { 130 | Log.i(TAG,"addToMemoryCache"); 131 | mMemoryCache.put(key, bitmap); 132 | } 133 | } 134 | 135 | /** 136 | * 首选 1 137 | * 根据key值获取内存中的bitmap 138 | * @param key 139 | * @return 140 | */ 141 | private Bitmap loadFromMemoryCache(String key) { 142 | return mMemoryCache.get(key); 143 | } 144 | 145 | /** 146 | * 次选 2 147 | * 从文件缓存中获取图片 (这个步骤会对返回和内存缓存的bitmap进行压缩) 148 | * @param url url(也是存取图片的key) 149 | * @param dstWidth 150 | * @param dstHeight 151 | * @return 152 | * @throws IOException 153 | */ 154 | private Bitmap loadFromDisk(String url, int dstWidth, int dstHeight) throws IOException { 155 | if (Looper.myLooper() == Looper.getMainLooper()) { 156 | Log.i(TAG, "should not Bitmap in main thread"); 157 | } 158 | if (mDiskLruCache == null) { 159 | return null; 160 | } 161 | Bitmap bitmap = null; 162 | String key = getKeyFromUrl(url); 163 | DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key); 164 | if (snapshot != null) { 165 | FileInputStream fileInputStream = (FileInputStream) snapshot.getInputStream(0); 166 | FileDescriptor fileDescriptor = fileInputStream.getFD(); 167 | bitmap = decodeSampledBitmapFromFD(fileDescriptor, dstWidth, dstHeight); 168 | if (bitmap != null) { 169 | addToMemoryCache(key, bitmap); 170 | } 171 | } 172 | return bitmap; 173 | } 174 | 175 | /** 176 | * 最后选 3 177 | * 运行内存和文件中都没有获取到后就从网络获取 178 | * 从网络获取成功后添加到 文件缓存 和 内存缓存 179 | * @param url 180 | * @param dstWidth 181 | * @param dstHeight 182 | * @return 183 | * @throws IOException 184 | */ 185 | private Bitmap loadFromNet(String url, int dstWidth, int dstHeight) throws IOException { 186 | if (Looper.myLooper() == Looper.getMainLooper()) { 187 | throw new RuntimeException("Do not load Bitmap in main thread."); 188 | } 189 | if (mDiskLruCache == null) { 190 | return null; 191 | } 192 | String key = getKeyFromUrl(url); 193 | DiskLruCache.Editor editor = mDiskLruCache.edit(key); 194 | if (editor != null) { 195 | OutputStream outputStream = editor.newOutputStream(0); 196 | if (addToDiskFromUrl(url, outputStream)) { 197 | editor.commit(); 198 | } else { 199 | editor.abort(); 200 | } 201 | mDiskLruCache.flush(); 202 | } 203 | return loadFromDisk(url, dstWidth, dstHeight); 204 | } 205 | 206 | /** 207 | * 根据url用http的方式获取图片 写入到DiskLruCache的输出流 208 | * @param urlString 209 | * @param outputStream 写到DiskLruCache的输出流 210 | * @return 211 | */ 212 | public boolean addToDiskFromUrl(String urlString, OutputStream outputStream) { 213 | HttpURLConnection urlConnection = null; 214 | BufferedInputStream bis = null; 215 | BufferedOutputStream bos = null; 216 | 217 | try { 218 | final URL url = new URL(urlString); 219 | urlConnection = (HttpURLConnection) url.openConnection(); 220 | bis = new BufferedInputStream(urlConnection.getInputStream()); 221 | bos = new BufferedOutputStream(outputStream); 222 | 223 | int byteRead; 224 | while ((byteRead = bis.read()) != -1) { 225 | bos.write(byteRead); 226 | } 227 | return true; 228 | }catch (IOException e) { 229 | e.printStackTrace(); 230 | } finally { 231 | if (urlConnection != null) { 232 | urlConnection.disconnect(); 233 | } 234 | close(bis); 235 | close(bos); 236 | } 237 | return false; 238 | } 239 | 240 | /** 241 | * 关闭流 242 | * @param stream 243 | */ 244 | public void close(Closeable stream) { 245 | if (stream != null) { 246 | try { 247 | stream.close(); 248 | } catch (IOException e) { 249 | e.printStackTrace(); 250 | } 251 | } 252 | } 253 | /** 254 | * 压缩从文件取出的bitmap 255 | * @param fd 256 | * @param dstWidth 257 | * @param dstHeight 258 | * @return 259 | */ 260 | private Bitmap decodeSampledBitmapFromFD(FileDescriptor fd, int dstWidth, int dstHeight) { 261 | final BitmapFactory.Options options = new BitmapFactory.Options(); 262 | options.inJustDecodeBounds = true; 263 | BitmapFactory.decodeFileDescriptor(fd, null, options); 264 | options.inSampleSize = calInSampleSize(options, dstWidth, dstHeight); 265 | options.inJustDecodeBounds = false; 266 | return BitmapFactory.decodeFileDescriptor(fd, null, options); 267 | } 268 | /** 269 | * 根据给出的宽高计算图片的采样率 270 | * @param options 271 | * @param dstWidth 272 | * @param dstHeight 273 | * @return 274 | */ 275 | private int calInSampleSize(BitmapFactory.Options options, int dstWidth, int dstHeight) { 276 | //图片本来的宽高 277 | int rawWidth = options.outWidth; 278 | int rawHeight = options.outHeight; 279 | int inSampleSize = 1; 280 | //当图片本来的宽高 大于 需要的苦宽高 就减小采样率 281 | if (rawWidth > dstWidth || rawHeight > dstHeight) { 282 | float ratioWidth = (float) rawWidth / dstHeight; 283 | float ratioHeight = (float) rawHeight / dstHeight; 284 | inSampleSize = (int) Math.min(ratioWidth, ratioHeight); 285 | } 286 | return inSampleSize; 287 | } 288 | /** 289 | * 获取设备的缓存路径 290 | * @param context 291 | * @param dirName 缓存文件夹dx_cache_images 292 | * @return 293 | */ 294 | public File getDiskCacheDir(Context context, String dirName) { 295 | String cachePath; 296 | if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) 297 | || !Environment.isExternalStorageRemovable()) { 298 | cachePath = context.getExternalCacheDir().getPath(); 299 | } else { 300 | cachePath = context.getCacheDir().getPath(); 301 | } 302 | return new File(cachePath + File.separator + dirName); 303 | } 304 | 305 | /** 306 | * 获取app版本号 307 | * @param context 308 | * @return 309 | */ 310 | public int getAppVersion(Context context) { 311 | try { 312 | PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); 313 | return info.versionCode; 314 | } catch (PackageManager.NameNotFoundException e) { 315 | e.printStackTrace(); 316 | } 317 | return 1; 318 | } 319 | 320 | /** 321 | * 图片url作为存取的key做md5处理 322 | * @param url 323 | * @return 324 | */ 325 | private String getKeyFromUrl(String url) { 326 | String key; 327 | try { 328 | MessageDigest messageDigest = MessageDigest.getInstance("MD5"); 329 | messageDigest.update(url.getBytes()); 330 | byte[] m = messageDigest.digest(); 331 | return byteToString(m); 332 | } catch (NoSuchAlgorithmException e) { 333 | key = String.valueOf(url.hashCode()); 334 | } 335 | return key; 336 | } 337 | 338 | /** 339 | * byte To String 340 | * @param b 341 | * @return 342 | */ 343 | private static String byteToString(byte[] b){ 344 | StringBuffer sb = new StringBuffer(); 345 | for(int i = 0; i < b.length; i ++){ 346 | sb.append(b[i]); 347 | } 348 | return sb.toString(); 349 | } 350 | /** 351 | * step 1(必须步骤) 352 | * 加载图片的url地址,返回RequestCreator对象 353 | * @param url 354 | * @return 355 | */ 356 | public RequestCreator load(String url) { 357 | return new RequestCreator(url); 358 | } 359 | 360 | /** 361 | * 图片创建类 362 | */ 363 | public class RequestCreator implements Runnable{ 364 | 365 | String url; //图片请求url 366 | int holderResId; //默认显示的图片 367 | int errorResId; //加载失败的图片 368 | ImageView imageView; // 369 | int dstWidth; //期望的宽 370 | int dstHeight; //期望的高 371 | /** 372 | * 373 | * @param url 初始化图片的url地址 374 | */ 375 | public RequestCreator(String url) { 376 | this.url = url; 377 | } 378 | 379 | /** 380 | * step 2(可无步骤) 381 | * 设置默认图片,占位图片 382 | * @param holderResId 383 | */ 384 | public RequestCreator placeholder(int holderResId) { 385 | this.holderResId = holderResId; 386 | return this; 387 | } 388 | /** 389 | * step 3(可无步骤) 390 | * 发生错误加载的图篇 391 | * @param errorResId 392 | */ 393 | public RequestCreator error(int errorResId) { 394 | this.errorResId = errorResId; 395 | return this; 396 | } 397 | 398 | /** 399 | * step 4(必须步骤) 400 | * 提供设置图片的核心方法 401 | * 402 | * @param imageView 403 | */ 404 | public void into(ImageView imageView) { 405 | // 变成全局的 406 | this.imageView = imageView; 407 | if(imageView!=null){ 408 | this.dstWidth = imageView.getWidth(); 409 | this.dstHeight = imageView.getHeight(); 410 | } 411 | //向线程池中添加任务 412 | threadPoolExecutor.execute(this); 413 | } 414 | /** 415 | * step 4(必须步骤) 416 | * 提供设置图片的核心方法 417 | * 418 | * @param imageView 419 | */ 420 | public void into(ImageView imageView, int dstWidth, int dstHeight) { 421 | // 变成全局的 422 | this.imageView = imageView; 423 | this.dstWidth = dstWidth; 424 | this.dstHeight = dstHeight; 425 | //向线程池中添加任务 426 | threadPoolExecutor.execute(this); 427 | } 428 | @Override 429 | public void run() { 430 | //开始加载图片 431 | try { 432 | //显示占位图 433 | if(holderResId!=0){ 434 | displayImage(holderResId); 435 | } 436 | Bitmap bitmap; 437 | Log.i(TAG, "开始任务"); 438 | String key = getKeyFromUrl(url); 439 | bitmap = loadFromMemoryCache(key); 440 | if (bitmap != null) { 441 | Log.i(TAG, "loadFromMemoryCache"); 442 | displayImage(bitmap); 443 | return; 444 | } 445 | bitmap = loadFromDisk(url, dstWidth, dstHeight); 446 | if (bitmap != null) { 447 | Log.i(TAG, "loadFromDisk"); 448 | displayImage(bitmap); 449 | return; 450 | } 451 | bitmap = loadFromNet(url, dstWidth, dstHeight); 452 | if (bitmap != null) { 453 | Log.i(TAG, "loadFromNet"); 454 | displayImage(bitmap); 455 | return; 456 | } 457 | if(0 != errorResId) displayImage(errorResId); 458 | } catch (IOException e) { 459 | e.printStackTrace(); 460 | displayImage(errorResId); 461 | } 462 | } 463 | private void displayImage(final Bitmap bitmap){ 464 | mMainHandler.post(new Runnable() { 465 | @Override 466 | public void run() { 467 | imageView.setImageBitmap(bitmap); 468 | } 469 | }); 470 | } 471 | private void displayImage(final int resId){ 472 | mMainHandler.post(new Runnable() { 473 | @Override 474 | public void run() { 475 | imageView.setImageResource(resId); 476 | } 477 | }); 478 | } 479 | } 480 | 481 | /** 482 | * 取消所有任务 483 | */ 484 | public void cancelAllTask(){ 485 | if(mMainHandler!=null){ 486 | mMainHandler.removeCallbacksAndMessages(null); 487 | } 488 | if(threadPoolExecutor!=null){ 489 | threadPoolExecutor.shutdownNow(); 490 | } 491 | } 492 | } 493 | -------------------------------------------------------------------------------- /Lib_ImageLoader/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | DXImageLoader 3 | 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AndroidBitmapOperations 2 | Android bitmap clip, scale, blur, rotate and so on. java and jni realize. 图片裁剪、缩放、模糊、旋转的java和jni实现。 3 | # 1、Bitmap碎片复用的情况下任意裁剪 4 | 5 | 裁剪讲解博客地址:https://www.jianshu.com/p/329746c1789f 6 | 7 | 这里说的`碎片复用`就是在图片的`裁剪过程中`会`创建`和`丢弃`大量的`Bitmap对象`,如果不对这些Bitmap进行复用会造成多余的`内存浪费`,造成`内存抖动`。 8 | 9 | 10 | ### 1.1 Bitmap裁剪保留下部分: 11 | |说明 | 前后效果对比 | 12 | | ---- | ----- | 13 | | 裁剪保留下部分,取一半高度 | ![TIM图片20180228220755.png](http://upload-images.jianshu.io/upload_images/1813550-d95bf47ac2d79ed0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) **裁剪后:** ![Screenshot_2018-02-28-21-59-16-052_BitmapKit.png](http://upload-images.jianshu.io/upload_images/1813550-26e1f0c48c4624b7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) | 14 | 15 | 裁剪代码: 16 | ```java 17 | /** 18 | * 裁剪一定高度保留下面 19 | * @param srcBitmap 20 | * @param needHeight 21 | * @param recycleSrc 是否回收原图 22 | * @return 23 | */ 24 | @DebugLog 25 | public static Bitmap cropBitmapBottom(Bitmap srcBitmap, int needHeight, boolean recycleSrc) { 26 | 27 | Log.d("danxx", "cropBitmapBottom before h : "+srcBitmap.getHeight()); 28 | 29 | /**裁剪保留下部分的第一个像素的Y坐标*/ 30 | int needY = srcBitmap.getHeight() - needHeight; 31 | 32 | /**裁剪关键步骤*/ 33 | Bitmap cropBitmap = Bitmap.createBitmap(srcBitmap,0,needY,srcBitmap.getWidth(),needHeight); 34 | 35 | Log.d("danxx", "cropBitmapBottom after h : "+cropBitmap.getHeight()); 36 | 37 | /**回收之前的Bitmap*/ 38 | if (recycleSrc && srcBitmap != null && !srcBitmap.equals(cropBitmap) && !srcBitmap.isRecycled()) { 39 | GlideBitmapPool.putBitmap(srcBitmap); 40 | } 41 | 42 | return cropBitmap; 43 | } 44 | ``` 45 | 46 | ### 1.2 Bitmap裁剪保留左部分: 47 | |说明 | 前后效果对比 | 48 | | ---- | ----- | 49 | | 裁剪保留左部分,取一半宽度 | ![TIM图片20180228220755.png](http://upload-images.jianshu.io/upload_images/1813550-d95bf47ac2d79ed0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) **裁剪后:** ![Screenshot_2018-02-28-21-59-34-089_BitmapKit.png](http://upload-images.jianshu.io/upload_images/1813550-18ca231f0bea41ab.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) | 50 | 51 | 裁剪代码: 52 | ```java 53 | /** 54 | * 裁剪一定高度保留左边 55 | * @param srcBitmap 56 | * @param needWidth 57 | * @return 58 | */ 59 | @DebugLog 60 | public static Bitmap cropBitmapLeft(Bitmap srcBitmap, int needWidth, boolean recycleSrc) { 61 | 62 | Log.d("danxx", "cropBitmapLeft before w : "+srcBitmap.getWidth()); 63 | 64 | /**裁剪关键步骤*/ 65 | Bitmap cropBitmap = Bitmap.createBitmap(srcBitmap, 0, 0, needWidth, srcBitmap.getHeight()); 66 | 67 | Log.d("danxx", "cropBitmapLeft after w : "+cropBitmap.getWidth()); 68 | 69 | /**回收之前的Bitmap*/ 70 | if (recycleSrc && srcBitmap != null && !srcBitmap.equals(cropBitmap) && !srcBitmap.isRecycled()) { 71 | GlideBitmapPool.putBitmap(srcBitmap); 72 | } 73 | 74 | return cropBitmap; 75 | } 76 | ``` 77 | 78 | 79 | ### 1.3 Bitmap裁剪保留右部分: 80 | |说明 | 前后效果对比 | 81 | | ---- | ----- | 82 | | 裁剪保留右部分,取一半宽度 | ![TIM图片20180228220755.png](http://upload-images.jianshu.io/upload_images/1813550-d95bf47ac2d79ed0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) **裁剪后:** ![Screenshot_2018-02-28-22-00-03-095_BitmapKit.png](http://upload-images.jianshu.io/upload_images/1813550-db59e75c670a7268.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) | 83 | 84 | 裁剪代码: 85 | ```java 86 | /** 87 | * 裁剪一定高度保留左边 88 | * @param srcBitmap 89 | * @param needWidth 90 | * @return 91 | */ 92 | @DebugLog 93 | public static Bitmap cropBitmapRight(Bitmap srcBitmap, int needWidth, boolean recycleSrc) { 94 | 95 | Log.d("danxx", "cropBitmapRight before w : "+srcBitmap.getWidth()); 96 | 97 | int needX = srcBitmap.getWidth() - needWidth; 98 | 99 | /**裁剪关键步骤*/ 100 | Bitmap cropBitmap = Bitmap.createBitmap(srcBitmap, needX, 0, needWidth, srcBitmap.getHeight()); 101 | 102 | Log.d("danxx", "cropBitmapRight after w : "+cropBitmap.getWidth()); 103 | 104 | /**回收之前的Bitmap*/ 105 | if (recycleSrc && srcBitmap != null && !srcBitmap.equals(cropBitmap) && !srcBitmap.isRecycled()) { 106 | GlideBitmapPool.putBitmap(srcBitmap); 107 | } 108 | 109 | return cropBitmap; 110 | } 111 | 112 | ``` 113 | 114 | 115 | ### 1.4 Bitmap裁剪保留上部分: 116 | |说明 | 前后效果对比 | 117 | | ---- | ----- | 118 | | 裁剪保留上部分,取一半高度 | ![TIM图片20180228220755.png](http://upload-images.jianshu.io/upload_images/1813550-d95bf47ac2d79ed0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) **裁剪后:** ![Screenshot_2018-02-28-21-59-49-769_BitmapKit.png](http://upload-images.jianshu.io/upload_images/1813550-b578fd3f5884a3a9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 119 | | 120 | 121 | 裁剪代码: 122 | ```java 123 | /** 124 | * 裁剪一定高度保留下面 125 | * @param srcBitmap 126 | * @param needHeight 127 | * @param recycleSrc 是否回收原图 128 | * @return 129 | */ 130 | @DebugLog 131 | public static Bitmap cropBitmapTop(Bitmap srcBitmap, int needHeight, boolean recycleSrc) { 132 | 133 | Log.d("danxx", "cropBitmapBottom before h : "+srcBitmap.getHeight()); 134 | 135 | /**裁剪保留上部分的第一个像素的Y坐标*/ 136 | int needY = 0; 137 | 138 | /**裁剪关键步骤*/ 139 | Bitmap cropBitmap = Bitmap.createBitmap(srcBitmap,0,needY,srcBitmap.getWidth(),needHeight); 140 | 141 | Log.d("danxx", "cropBitmapBottom after h : "+cropBitmap.getHeight()); 142 | 143 | /**回收之前的Bitmap*/ 144 | if (recycleSrc && srcBitmap != null && !srcBitmap.equals(cropBitmap) && !srcBitmap.isRecycled()) { 145 | GlideBitmapPool.putBitmap(srcBitmap); 146 | } 147 | 148 | return cropBitmap; 149 | } 150 | 151 | 152 | ``` 153 | 154 | ### 1.5 Bitmap指定参数任意裁剪: 155 | |说明 | 前后效果对比 | 156 | | ---- | ----- | 157 | | 指定参数任意裁剪 | ![TIM图片20180228220755.png](http://upload-images.jianshu.io/upload_images/1813550-d95bf47ac2d79ed0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) **裁剪后:** ![Screenshot_2018-02-28-22-00-13-606_BitmapKit.png](http://upload-images.jianshu.io/upload_images/1813550-2295ffb4c3b8a4b2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) | 158 | 159 | 裁剪代码: 160 | ```java 161 | /** 162 | * 自定义裁剪,根据第一个像素点(左上角)X和Y轴坐标和需要的宽高来裁剪 163 | * @param srcBitmap 164 | * @param firstPixelX 165 | * @param firstPixelY 166 | * @param needWidth 167 | * @param needHeight 168 | * @param recycleSrc 169 | * @return 170 | */ 171 | @DebugLog 172 | public static Bitmap cropBitmapCustom(Bitmap srcBitmap, int firstPixelX, int firstPixelY, int needWidth, int needHeight, boolean recycleSrc) { 173 | 174 | Log.d("danxx", "cropBitmapRight before w : "+srcBitmap.getWidth()); 175 | Log.d("danxx", "cropBitmapRight before h : "+srcBitmap.getHeight()); 176 | 177 | if(firstPixelX + needWidth > srcBitmap.getWidth()){ 178 | needWidth = srcBitmap.getWidth() - firstPixelX; 179 | } 180 | 181 | if(firstPixelY + needHeight > srcBitmap.getHeight()){ 182 | needHeight = srcBitmap.getHeight() - firstPixelY; 183 | } 184 | 185 | /**裁剪关键步骤*/ 186 | Bitmap cropBitmap = Bitmap.createBitmap(srcBitmap, firstPixelX, firstPixelY, needWidth, needHeight); 187 | 188 | Log.d("danxx", "cropBitmapRight after w : "+cropBitmap.getWidth()); 189 | Log.d("danxx", "cropBitmapRight after h : "+cropBitmap.getHeight()); 190 | 191 | 192 | /**回收之前的Bitmap*/ 193 | if (recycleSrc && srcBitmap != null && !srcBitmap.equals(cropBitmap) && !srcBitmap.isRecycled()) { 194 | GlideBitmapPool.putBitmap(srcBitmap); 195 | } 196 | 197 | return cropBitmap; 198 | } 199 | 200 | ``` -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'com.jakewharton.hugo' 3 | 4 | android { 5 | compileSdkVersion 26 6 | defaultConfig { 7 | applicationId "com.danxx" 8 | minSdkVersion 15 9 | targetSdkVersion 26 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | 14 | renderscriptTargetApi 18 15 | renderscriptSupportModeEnabled true 16 | 17 | } 18 | 19 | dataBinding { 20 | enabled = true 21 | } 22 | 23 | buildTypes { 24 | release { 25 | minifyEnabled false 26 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 27 | } 28 | } 29 | } 30 | 31 | dependencies { 32 | implementation fileTree(include: ['*.jar'], dir: 'libs') 33 | implementation 'com.android.support:appcompat-v7:26.1.0' 34 | implementation 'com.android.support.constraint:constraint-layout:1.0.2' 35 | testImplementation 'junit:junit:4.12' 36 | androidTestImplementation 'com.android.support.test:runner:1.0.1' 37 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' 38 | compile 'io.reactivex.rxjava2:rxjava:2.1.8' 39 | compile 'com.facebook.fresco:fresco:1.7.1' 40 | implementation 'com.android.support:recyclerview-v7:26.1.0' 41 | implementation project(':Lib_BitmapKit') 42 | implementation project(':Lib_ImageLoader') 43 | compile 'com.bulong.rudeness:rudeness:latest.release@aar' 44 | } 45 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/danxx/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.danxx; 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 | * Instrumented 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.danxx", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /app/src/main/java/com/danxx/ActivityCrop.java: -------------------------------------------------------------------------------- 1 | package com.danxx; 2 | 3 | import android.support.v7.app.AppCompatActivity; 4 | import android.os.Bundle; 5 | 6 | public class ActivityCrop extends AppCompatActivity { 7 | 8 | @Override 9 | protected void onCreate(Bundle savedInstanceState) { 10 | super.onCreate(savedInstanceState); 11 | setContentView(R.layout.activity_crop); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/danxx/ActivitySnapRecycler.java: -------------------------------------------------------------------------------- 1 | package com.danxx; 2 | 3 | import android.databinding.DataBindingUtil; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.support.v7.widget.LinearSnapHelper; 8 | import android.support.v7.widget.RecyclerView; 9 | import android.util.Log; 10 | import android.view.LayoutInflater; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | 14 | import com.danxx.databinding.ActivitySnapRecyclerBinding; 15 | import com.danxx.view.ShadowDraweeView; 16 | import com.facebook.drawee.view.SimpleDraweeView; 17 | 18 | import java.util.ArrayList; 19 | import java.util.Arrays; 20 | import java.util.List; 21 | 22 | /** 23 | * Created by danxx on 2018/1/15. 24 | */ 25 | 26 | public class ActivitySnapRecycler extends AppCompatActivity { 27 | 28 | private List urlList; 29 | 30 | RecyclerView recyclerView; 31 | LinearSnapHelper linearSnapHelper; 32 | ActivitySnapRecyclerBinding snapRecyclerBinding; 33 | TangweiAdapter tangweiAdapter; 34 | 35 | View selectedView; 36 | 37 | @Override 38 | protected void onCreate(@Nullable Bundle savedInstanceState) { 39 | super.onCreate(savedInstanceState); 40 | 41 | snapRecyclerBinding = 42 | DataBindingUtil.setContentView(ActivitySnapRecycler.this, R.layout.activity_snap_recycler); 43 | 44 | urlList = new ArrayList<>( 45 | Arrays.asList( 46 | getResources().getString(R.string.tangwei01), 47 | getResources().getString(R.string.tangwei02), 48 | getResources().getString(R.string.tangwei03), 49 | getResources().getString(R.string.tangwei04), 50 | getResources().getString(R.string.tangwei05), 51 | getResources().getString(R.string.tangwei06) 52 | ) 53 | ); 54 | 55 | tangweiAdapter = new TangweiAdapter(); 56 | tangweiAdapter.setData(urlList); 57 | 58 | linearSnapHelper = new LinearSnapHelper(); 59 | linearSnapHelper.attachToRecyclerView(snapRecyclerBinding.recyclerView); 60 | 61 | snapRecyclerBinding.recyclerView.setAdapter(tangweiAdapter); 62 | 63 | snapRecyclerBinding.recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { 64 | @Override 65 | public void onScrollStateChanged(RecyclerView recyclerView, int newState) { 66 | super.onScrollStateChanged(recyclerView, newState); 67 | if(newState == RecyclerView.SCROLL_STATE_IDLE){ 68 | View view = linearSnapHelper.findSnapView(snapRecyclerBinding.recyclerView.getLayoutManager()); 69 | if(view!=null){ 70 | if(selectedView!=null){ 71 | if(selectedView.equals(view)){ 72 | if(!selectedView.isEnabled()){ 73 | selectedView.setSelected(true); 74 | } 75 | }else { 76 | selectedView.setSelected(false); 77 | view.setSelected(true); 78 | selectedView = view; 79 | } 80 | }else { 81 | selectedView = view; 82 | selectedView.setSelected(true); 83 | } 84 | } 85 | } 86 | } 87 | }); 88 | 89 | snapRecyclerBinding.recyclerView.postDelayed(new Runnable() { 90 | @Override 91 | public void run() { 92 | if(snapRecyclerBinding.recyclerView.getChildCount()>0){ 93 | View firstView = snapRecyclerBinding.recyclerView.getChildAt(0); 94 | if(firstView!=null){ 95 | selectedView = firstView; 96 | firstView.setSelected(true); 97 | } 98 | } 99 | } 100 | },1000); 101 | } 102 | 103 | static class TangweiAdapter extends RecyclerView.Adapter{ 104 | 105 | List url = new ArrayList<>(); 106 | 107 | public void setData(List data){ 108 | this.url = data; 109 | } 110 | 111 | @Override 112 | public TangweiViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 113 | View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_recycler_item,parent,false); 114 | return new TangweiViewHolder(view); 115 | } 116 | 117 | @Override 118 | public void onBindViewHolder(TangweiViewHolder holder, int position) { 119 | holder.draweeView.setImageURI(url.get(position)); 120 | } 121 | 122 | @Override 123 | public int getItemCount() { 124 | return url == null ? 0 : url.size(); 125 | } 126 | } 127 | 128 | static class TangweiViewHolder extends RecyclerView.ViewHolder { 129 | SimpleDraweeView draweeView; 130 | public TangweiViewHolder(View itemView) { 131 | super(itemView); 132 | draweeView = (SimpleDraweeView) itemView; 133 | } 134 | } 135 | 136 | } 137 | -------------------------------------------------------------------------------- /app/src/main/java/com/danxx/DanxxApp.java: -------------------------------------------------------------------------------- 1 | package com.danxx; 2 | 3 | import android.app.Application; 4 | 5 | import com.bulong.rudeness.RudenessScreenHelper; 6 | import com.facebook.drawee.backends.pipeline.Fresco; 7 | 8 | import danxx.bitmapkit.BitmapKitConfig; 9 | 10 | /** 11 | * Created by Administrator on 2018/1/15. 12 | */ 13 | 14 | public class DanxxApp extends Application { 15 | 16 | @Override 17 | public void onCreate() { 18 | super.onCreate(); 19 | 20 | Fresco.initialize(this); 21 | BitmapKitConfig.initialize(this, 5*1024*1024); 22 | 23 | //设计图标注的宽度 24 | int designWidth = 1080; 25 | new RudenessScreenHelper(this, designWidth).activate(); 26 | 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/danxx/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.danxx; 2 | 3 | import android.content.Intent; 4 | import android.graphics.Color; 5 | import android.graphics.ColorFilter; 6 | import android.graphics.PorterDuff; 7 | import android.support.v7.app.AppCompatActivity; 8 | import android.os.Bundle; 9 | import android.support.v7.widget.LinearSnapHelper; 10 | import android.util.DisplayMetrics; 11 | import android.view.Display; 12 | import android.view.MotionEvent; 13 | import android.view.View; 14 | import android.widget.Button; 15 | 16 | import danxx.bitmapkit.blur.BlurKit; 17 | 18 | /** 19 | * @author danxx 20 | */ 21 | public class MainActivity extends AppCompatActivity { 22 | 23 | Button btn; 24 | 25 | @Override 26 | protected void onCreate(Bundle savedInstanceState) { 27 | super.onCreate(savedInstanceState); 28 | 29 | BlurKit.init(MainActivity.this); 30 | 31 | setContentView(R.layout.activity_main); 32 | 33 | btn = findViewById(R.id.btn); 34 | 35 | btn.setOnTouchListener(new View.OnTouchListener() { 36 | @Override 37 | public boolean onTouch(View view, MotionEvent event) { 38 | Button touchedButton = (Button)view; 39 | switch (event.getAction()) { 40 | case MotionEvent.ACTION_DOWN: 41 | touchedButton.getBackground().setColorFilter(0x22000000, PorterDuff.Mode.SRC_ATOP); 42 | touchedButton.invalidate(); 43 | break; 44 | case MotionEvent.ACTION_CANCEL: 45 | case MotionEvent.ACTION_UP: 46 | touchedButton.getBackground().clearColorFilter(); 47 | touchedButton.invalidate(); 48 | Intent intent = new Intent(MainActivity.this, ActivitySnapRecycler.class); 49 | startActivity(intent); 50 | break; 51 | } 52 | return true; 53 | 54 | } 55 | }); 56 | 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/src/main/java/com/danxx/demoview/BlurImageView.java: -------------------------------------------------------------------------------- 1 | package com.danxx.demoview; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Bitmap; 6 | import android.graphics.BitmapFactory; 7 | import android.graphics.Canvas; 8 | import android.graphics.Paint; 9 | import android.graphics.Rect; 10 | import android.graphics.drawable.Drawable; 11 | import android.support.annotation.Nullable; 12 | import android.util.AttributeSet; 13 | import android.view.View; 14 | 15 | import com.danxx.R; 16 | 17 | import danxx.bitmapkit.blur.ShadeUtil; 18 | 19 | /** 20 | * Created by danxx on 2018/1/29. 21 | * 22 | * @Desc Bitmap高斯模糊示例控件 23 | */ 24 | 25 | public class BlurImageView extends View { 26 | 27 | Bitmap bitmap,blurBitmap; 28 | Paint paint; 29 | private Drawable shaderDrawable; 30 | private Rect currentRect; 31 | private int shaderPadding = ShadeUtil.dip2px(BlurImageView.this.getContext(), 19); 32 | 33 | public BlurImageView(Context context) { 34 | this(context, null); 35 | } 36 | 37 | public BlurImageView(Context context, @Nullable AttributeSet attrs) { 38 | this(context, attrs, 0); 39 | } 40 | 41 | public BlurImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 42 | super(context, attrs, defStyleAttr); 43 | init(context, attrs, defStyleAttr); 44 | } 45 | 46 | private void init(Context context, AttributeSet attrs, int defStyleAttr) { 47 | 48 | final TypedArray a = 49 | context.obtainStyledAttributes(attrs, R.styleable.DemoImageView, defStyleAttr, 0); 50 | 51 | int bitmapId = a.getResourceId(R.styleable.DemoImageView_bitmapSrc, R.drawable.gaoyuanyuan); 52 | 53 | bitmap = BitmapFactory.decodeResource(getResources(), bitmapId); 54 | 55 | shaderDrawable = getResources().getDrawable(R.drawable.shadow_5); 56 | 57 | currentRect = new Rect(); 58 | } 59 | 60 | 61 | @Override 62 | protected void onDraw(Canvas canvas) { 63 | getDrawingRect(currentRect); 64 | super.onDraw(canvas); 65 | 66 | if(blurBitmap == null){ 67 | blurBitmap = ShadeUtil.createShadeBitmap(canvas, bitmap, shaderPadding, shaderDrawable, currentRect, 16, true); 68 | }else { 69 | blurBitmap = ShadeUtil.createShadeBitmap(canvas, bitmap, shaderPadding, shaderDrawable, currentRect, 16, false); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /app/src/main/java/com/danxx/demoview/CropImageView.java: -------------------------------------------------------------------------------- 1 | package com.danxx.demoview; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Bitmap; 6 | import android.graphics.BitmapFactory; 7 | import android.graphics.Canvas; 8 | import android.graphics.Paint; 9 | import android.util.AttributeSet; 10 | import android.view.View; 11 | import com.danxx.R; 12 | import danxx.bitmapkit.crop.BitmapCropUtil; 13 | 14 | /** 15 | * Created by danxx on 2018/1/26. 16 | * Bitmap裁剪示例View 17 | */ 18 | 19 | public class CropImageView extends View { 20 | 21 | Bitmap bitmap; 22 | int needLength; 23 | Paint paint; 24 | 25 | public CropImageView(Context context) { 26 | this(context,null); 27 | } 28 | 29 | public CropImageView(Context context, AttributeSet attrs) { 30 | this(context, attrs,0); 31 | } 32 | 33 | public CropImageView(Context context, AttributeSet attrs, int defStyleAttr) { 34 | super(context, attrs, defStyleAttr); 35 | init(context,attrs,defStyleAttr); 36 | } 37 | 38 | private void init(Context context, AttributeSet attrs, int defStyleAttr){ 39 | 40 | final TypedArray a = 41 | context.obtainStyledAttributes(attrs, R.styleable.DemoImageView, defStyleAttr, 0); 42 | 43 | int bitmapId = a.getResourceId(R.styleable.DemoImageView_bitmapSrc,R.drawable.gaoyuanyuan); 44 | 45 | int cropType = a.getInteger(R.styleable.DemoImageView_bitmapCropType,1); 46 | 47 | bitmap = BitmapFactory.decodeResource(getResources(), bitmapId); 48 | 49 | paint = new Paint(); 50 | 51 | switch (cropType){ 52 | case 1: 53 | needLength = bitmap.getWidth()/2; 54 | bitmap = BitmapCropUtil.cropBitmapLeft(bitmap,needLength); 55 | break; 56 | case 2: 57 | needLength = bitmap.getHeight()/2; 58 | bitmap = BitmapCropUtil.cropBitmapTop(bitmap,needLength); 59 | break; 60 | case 3: 61 | needLength = bitmap.getHeight()/2; 62 | bitmap = BitmapCropUtil.cropBitmapBottom(bitmap,needLength); 63 | break; 64 | case 4: 65 | needLength = bitmap.getWidth()/2; 66 | bitmap = BitmapCropUtil.cropBitmapRight(bitmap,needLength); 67 | break; 68 | case 5: 69 | int needW = bitmap.getWidth()/2; 70 | int needH = bitmap.getHeight()/2; 71 | bitmap = BitmapCropUtil.cropBitmapCustom(bitmap,140, 80,needW, needH); 72 | break; 73 | } 74 | 75 | a.recycle(); 76 | 77 | } 78 | 79 | @Override 80 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 81 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 82 | setMeasuredDimension(bitmap.getWidth(),bitmap.getHeight()); 83 | } 84 | 85 | @Override 86 | protected void onDraw(Canvas canvas) { 87 | super.onDraw(canvas); 88 | if(bitmap!=null){ 89 | canvas.drawBitmap(bitmap,0,0,paint); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /app/src/main/java/com/danxx/demoview/PhotoDraweeView.java: -------------------------------------------------------------------------------- 1 | package com.danxx.demoview; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | 6 | import com.facebook.drawee.generic.GenericDraweeHierarchy; 7 | import com.facebook.drawee.view.SimpleDraweeView; 8 | 9 | /** 10 | * Created by danxx on 2018/1/28. 11 | */ 12 | 13 | public class PhotoDraweeView extends SimpleDraweeView{ 14 | 15 | public PhotoDraweeView(Context context, GenericDraweeHierarchy hierarchy) { 16 | super(context, hierarchy); 17 | } 18 | 19 | public PhotoDraweeView(Context context) { 20 | super(context); 21 | } 22 | 23 | public PhotoDraweeView(Context context, AttributeSet attrs) { 24 | super(context, attrs); 25 | } 26 | 27 | public PhotoDraweeView(Context context, AttributeSet attrs, int defStyle) { 28 | super(context, attrs, defStyle); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/danxx/utils/BitmapClipUtils.java: -------------------------------------------------------------------------------- 1 | package com.danxx.utils; 2 | 3 | import android.graphics.Bitmap; 4 | import android.util.Log; 5 | 6 | import hugo.weaving.DebugLog; 7 | 8 | /** 9 | * Created by danxx on 2018/1/12. 10 | * 图片裁剪工具类 11 | */ 12 | 13 | public class BitmapClipUtils { 14 | 15 | /** 16 | * 按竖长方形裁切图片 17 | * 18 | * @param bitmap 19 | * @return 20 | */ 21 | @DebugLog 22 | public static Bitmap clipBitmapRect(Bitmap bitmap) { 23 | if (bitmap == null) { 24 | return null; 25 | } 26 | int w = bitmap.getWidth(); // 得到图片的宽,高 27 | int h = bitmap.getHeight(); 28 | 29 | Log.d("danxx","before w : "+w); 30 | Log.d("danxx","before h : "+h); 31 | 32 | int nw, nh, retX, retY; 33 | if (w > h) { //横长方形 34 | nw = h / 2; 35 | nh = h; 36 | retX = (w - nw) / 2; 37 | retY = 0; 38 | } else { //竖长方形 39 | nw = w / 2; 40 | nh = w; 41 | retX = w / 4; 42 | retY = (h - w) / 2; 43 | } 44 | 45 | // 下面这句是关键 46 | Bitmap bmp = Bitmap.createBitmap(bitmap, retX, retY, nw, nh, null,false); 47 | 48 | Log.d("danxx","after w : "+bmp.getWidth()); 49 | Log.d("danxx","after h : "+bmp.getHeight()); 50 | 51 | if (bitmap != null && !bitmap.equals(bmp) && !bitmap.isRecycled()) { 52 | bitmap.recycle(); 53 | bitmap = null; 54 | } 55 | return bmp; 56 | } 57 | 58 | 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/java/com/danxx/view/RoundBlurShaderView.java: -------------------------------------------------------------------------------- 1 | package com.danxx.view; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.BitmapFactory; 6 | import android.graphics.Canvas; 7 | import android.graphics.ColorFilter; 8 | import android.graphics.Paint; 9 | import android.graphics.Rect; 10 | import android.graphics.RectF; 11 | import android.support.annotation.Nullable; 12 | import android.util.AttributeSet; 13 | import android.util.Log; 14 | import android.view.View; 15 | 16 | import com.danxx.R; 17 | 18 | import danxx.bitmapkit.ShaderRoundUtil; 19 | import hugo.weaving.DebugLog; 20 | 21 | /** 22 | * Created by danxx on 2018/1/14. 23 | */ 24 | 25 | public class RoundBlurShaderView extends View { 26 | 27 | Paint paint = new Paint(); 28 | Bitmap bitmap; 29 | Rect rect = new Rect(); 30 | 31 | public RoundBlurShaderView(Context context) { 32 | this(context, null); 33 | } 34 | 35 | public RoundBlurShaderView(Context context, @Nullable AttributeSet attrs) { 36 | this(context, attrs, 0); 37 | } 38 | 39 | public RoundBlurShaderView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 40 | super(context, attrs, defStyleAttr); 41 | 42 | init(); 43 | 44 | } 45 | 46 | private void init(){ 47 | 48 | paint.setAntiAlias(true); 49 | 50 | bitmap = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.bitmap_clip); 51 | 52 | if(bitmap != null){ 53 | Log.d("danxx", "BitmapFactory != null"); 54 | }else { 55 | Log.d("danxx", "BitmapFactory tempBitmap == null"); 56 | } 57 | 58 | } 59 | 60 | int shadowHeight = 180; 61 | 62 | @DebugLog 63 | @Override 64 | protected void onDraw(Canvas canvas) { 65 | super.onDraw(canvas); 66 | 67 | rect.set(0,0,bitmap.getWidth(),bitmap.getHeight()); 68 | 69 | bitmap = ShaderRoundUtil.processRoundBlurShader(canvas,bitmap,10,rect,shadowHeight); 70 | 71 | if(bitmap!=null){ 72 | 73 | Log.d("danxx", "drawBitmap----->"); 74 | 75 | RectF shadowRect = new RectF(rect); 76 | shadowRect.bottom = shadowHeight; 77 | 78 | canvas.drawBitmap(bitmap,null,shadowRect,paint); 79 | } 80 | 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /app/src/main/java/com/danxx/view/ShadowDraweeView.java: -------------------------------------------------------------------------------- 1 | package com.danxx.view; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.Canvas; 6 | import android.graphics.Color; 7 | import android.graphics.Paint; 8 | import android.graphics.PixelFormat; 9 | import android.graphics.Rect; 10 | import android.graphics.RectF; 11 | import android.graphics.drawable.Drawable; 12 | import android.support.annotation.Nullable; 13 | import android.support.v4.view.ViewCompat; 14 | import android.util.AttributeSet; 15 | import android.util.Log; 16 | 17 | import com.bulong.rudeness.RudenessScreenHelper; 18 | import com.facebook.drawee.generic.GenericDraweeHierarchy; 19 | import com.facebook.drawee.generic.RootDrawable; 20 | import com.facebook.drawee.view.SimpleDraweeView; 21 | import com.glidebitmappool.GlideBitmapPool; 22 | 23 | import danxx.bitmapkit.ShaderRoundUtils; 24 | import hugo.weaving.DebugLog; 25 | 26 | /** 27 | * @author danxx 28 | * @date 2018/1/12 29 | * @desc 30 | */ 31 | 32 | public class ShadowDraweeView extends SimpleDraweeView { 33 | 34 | private Bitmap blurBitmap; 35 | private Rect currentRect; 36 | private Paint paint; 37 | private int mRadius; 38 | private int shadowHeight = 120; 39 | 40 | public ShadowDraweeView(Context context, GenericDraweeHierarchy hierarchy) { 41 | super(context, hierarchy); 42 | init(); 43 | } 44 | 45 | public ShadowDraweeView(Context context) { 46 | super(context); 47 | init(); 48 | } 49 | 50 | public ShadowDraweeView(Context context, AttributeSet attrs) { 51 | super(context, attrs); 52 | init(); 53 | } 54 | 55 | public ShadowDraweeView(Context context, AttributeSet attrs, int defStyle) { 56 | super(context, attrs, defStyle); 57 | init(); 58 | } 59 | 60 | public ShadowDraweeView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 61 | super(context, attrs, defStyleAttr, defStyleRes); 62 | init(); 63 | } 64 | 65 | 66 | private void init() { 67 | Log.i("danxx", "ShadowDraweeView init"); 68 | currentRect = new Rect(); 69 | paint = new Paint(Paint.ANTI_ALIAS_FLAG); 70 | paint.setColor(Color.WHITE); 71 | paint.setStyle(Paint.Style.STROKE); 72 | paint.setStrokeWidth(4); 73 | mRadius = (int)RudenessScreenHelper.pt2px(getContext(), 15); 74 | shadowHeight = (int)RudenessScreenHelper.pt2px(getContext(), 100); 75 | } 76 | 77 | @DebugLog 78 | @Override 79 | protected void onDraw(Canvas canvas) { 80 | if (blurBitmap == null) { 81 | if (isSelected()) { 82 | getDrawingRect(currentRect); 83 | Drawable drawable = null; 84 | Drawable background = getBackground(); 85 | if (background == null) { 86 | Drawable draw = ((RootDrawable) getDrawable()).getDrawable(); 87 | if (draw != null) { 88 | drawable = draw; 89 | } 90 | } else { 91 | drawable = background; 92 | } 93 | if (drawable != null) { 94 | blurBitmap = drawable2bitmap(drawable); 95 | } 96 | if (blurBitmap != null && !blurBitmap.isRecycled()) { 97 | Log.i("danxx", "to drawRoundBlurShader"); 98 | blurBitmap = ShaderRoundUtils.processRoundBlurShader(blurBitmap, mRadius, currentRect, shadowHeight,true); 99 | Log.d("danxx","GlideBitmapPool to drawRoundBlurShader : "+blurBitmap); 100 | ShaderRoundUtils.drawRoundBlurShader(canvas, blurBitmap, currentRect); 101 | } else { 102 | Log.i("danxx", "getDrawingCache == null"); 103 | } 104 | } 105 | super.onDraw(canvas); 106 | 107 | } else { 108 | if (isSelected() && !blurBitmap.isRecycled()) { 109 | getDrawingRect(currentRect); 110 | ShaderRoundUtils.drawRoundBlurShader(canvas, blurBitmap, currentRect); 111 | } 112 | super.onDraw(canvas); 113 | 114 | } 115 | } 116 | 117 | private Bitmap drawable2bitmap(Drawable drawable) { 118 | // Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(), 119 | // drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 120 | // : Bitmap.Config.RGB_565); 121 | Bitmap bitmap = GlideBitmapPool.getBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888); 122 | Canvas canvas = new Canvas(bitmap); 123 | drawable.setBounds(0, 0, getWidth(), getHeight()); 124 | drawable.draw(canvas); 125 | return bitmap; 126 | } 127 | 128 | 129 | @Override 130 | public void setSelected(boolean selected) { 131 | super.setSelected(selected); 132 | if (selected) { 133 | ViewCompat.animate(this).scaleX(1.02f).scaleY(1.02f).setDuration(300).start(); 134 | } else { 135 | ViewCompat.animate(this).scaleX(1.0f).scaleY(1.0f).setDuration(300).start(); 136 | } 137 | } 138 | 139 | } 140 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/gaoyuanyuan.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dawish/BitmapKit/08df654085dd32faccb5e63ce2368cc6b9d56662/app/src/main/res/drawable-xhdpi/gaoyuanyuan.jpeg -------------------------------------------------------------------------------- /app/src/main/res/drawable/bitmap_clip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dawish/BitmapKit/08df654085dd32faccb5e63ce2368cc6b9d56662/app/src/main/res/drawable/bitmap_clip.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dawish/BitmapKit/08df654085dd32faccb5e63ce2368cc6b9d56662/app/src/main/res/drawable/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/loading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dawish/BitmapKit/08df654085dd32faccb5e63ce2368cc6b9d56662/app/src/main/res/drawable/loading.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/shadow_5.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dawish/BitmapKit/08df654085dd32faccb5e63ce2368cc6b9d56662/app/src/main/res/drawable/shadow_5.9.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_crop.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 22 | 23 | 28 | 29 | 36 | 37 | 41 | 42 | 49 | 50 | 54 | 55 | 62 | 63 | 67 | 68 | 75 | 76 | 81 | 82 | 89 | 94 | 95 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |