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 | | 裁剪保留下部分,取一半高度 |  **裁剪后:**  |
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 | | 裁剪保留左部分,取一半宽度 |  **裁剪后:**  |
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 | | 裁剪保留右部分,取一半宽度 |  **裁剪后:**  |
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 | | 裁剪保留上部分,取一半高度 |  **裁剪后:** 
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 | | 指定参数任意裁剪 |  **裁剪后:**  |
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 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_snap_recycler.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
14 |
15 |
21 |
22 |
32 |
33 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_recycler_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Dawish/BitmapKit/08df654085dd32faccb5e63ce2368cc6b9d56662/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Dawish/BitmapKit/08df654085dd32faccb5e63ce2368cc6b9d56662/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Dawish/BitmapKit/08df654085dd32faccb5e63ce2368cc6b9d56662/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Dawish/BitmapKit/08df654085dd32faccb5e63ce2368cc6b9d56662/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Dawish/BitmapKit/08df654085dd32faccb5e63ce2368cc6b9d56662/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Dawish/BitmapKit/08df654085dd32faccb5e63ce2368cc6b9d56662/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Dawish/BitmapKit/08df654085dd32faccb5e63ce2368cc6b9d56662/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Dawish/BitmapKit/08df654085dd32faccb5e63ce2368cc6b9d56662/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Dawish/BitmapKit/08df654085dd32faccb5e63ce2368cc6b9d56662/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Dawish/BitmapKit/08df654085dd32faccb5e63ce2368cc6b9d56662/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | BitmapKit
3 |
4 | https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1516032740812&di=e0b5e8f3cf289852ad2db8a8a38a04f4&imgtype=0&src=http%3A%2F%2Fp3.gexing.com%2FG1%2FM00%2F08%2FA4%2FrBACFFPHogLQ1M1iAACazVdtfPU058.jpg
5 | https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1516034230481&di=5def17ff516806a3a586c79d818826a1&imgtype=0&src=http%3A%2F%2Fimg.hkwb.net%2Fatt%2Fsite2%2F20110217%2F210913a79ed0bcd56a07990b7f25bebe.jpg
6 | https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1516034242598&di=a6ee67e1539b4f31cdf648f20a1142b3&imgtype=0&src=http%3A%2F%2Fwww.hinews.cn%2Fpic%2F0%2F15%2F12%2F60%2F15126049_979603.jpg
7 | http://img1.gtimg.com/news/pics/9730/9730990.jpg
8 | https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1516034286336&di=c789521d279ee80204854b4c4940a25e&imgtype=0&src=http%3A%2F%2Fimg.ivsky.com%2Fimg%2Fbizhi%2Fpre%2F201303%2F28%2Fwei_tang-007.jpg
9 | https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1516034342606&di=d1eeb90d4fbc6af0bf44c19a84cf9bba&imgtype=0&src=http%3A%2F%2Fimg5.duitang.com%2Fuploads%2Fitem%2F201409%2F30%2F20140930213936_JCjZE.jpeg
10 |
11 | 汤唯,1979年10月7日出生于浙江杭州,华语影视女演员,毕业于中央戏剧学院导演系本科。
12 | 2004年,主演了个人首部电影作品《警花燕子》,并凭借该片获得第六届电影频道数字电影百合奖“优秀女演员”奖 。2007年,因出演剧情片《色·戒》中王佳芝一角获得更多关注,并凭该片夺得第44届台湾电影金马奖最佳新人奖 。2010年,凭借爱情片《月满轩尼诗》获得第11届华语电影传媒大奖最佳女主角奖 。
13 | 2011年,汤唯凭借文艺片《晚秋》分别在韩国百想艺术大赏、韩国影评奖、釜山影评家奖等十余个评选中获得最佳女主角奖 。2013年,汤唯主演的爱情片《北京遇上西雅图》创造了华语爱情片票房纪录 ,并凭此片先后获得了上海影评人奖最佳女主角、中国电影导演协会年度女演员奖以及北京大学生电影节最佳女演员等多个奖项 。2014年,主演文艺爱情片《黄金时代》[8] 。2015年,主演动作片《骇客交锋》。2016年,其主演的爱情片《北京遇上西雅图之不二情书》取得了7.9亿元的票房成绩,再次创造了华语爱情片票房纪录。
14 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/test/java/com/danxx/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.danxx;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 |
5 | repositories {
6 | google()
7 | jcenter()
8 | }
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:3.0.1'
11 | classpath 'com.jakewharton.hugo:hugo-plugin:1.2.1'
12 |
13 | // NOTE: Do not place your application dependencies here; they belong
14 | // in the individual module build.gradle files
15 | }
16 | }
17 |
18 | allprojects {
19 | repositories {
20 | google()
21 | jcenter()
22 | }
23 | }
24 |
25 | task clean(type: Delete) {
26 | delete rootProject.buildDir
27 | }
28 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Dawish/BitmapKit/08df654085dd32faccb5e63ce2368cc6b9d56662/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Jan 08 21:15:20 CST 2018
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':Lib_ImageLoader', ':Lib_BitmapKit'
2 |
--------------------------------------------------------------------------------