Commit a18c79e0 authored by wanglei's avatar wanglei

...

parent 28ff03da
plugins {
id 'com.android.library'
id 'org.jetbrains.kotlin.android'
}
android {
namespace 'com.github.barteksc.pdfviewer' // 添加 namespace
compileSdkVersion 34 // 更新为最新 SDK 版本
defaultConfig {
minSdkVersion 21 // 提升最低 SDK 版本以符合现代应用标准
targetSdkVersion 34 // 更新为目标 SDK 版本
versionCode 1
versionName "2.8.1"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation 'com.github.barteksc:pdfium-android:1.7.1'
implementation("androidx.core:core-ktx:1.10.1")
implementation ("com.android.support:support-v4:28.0.0")
}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.github.barteksc.pdfviewer" >
<uses-sdk android:minSdkVersion="21" />
</manifest>
\ No newline at end of file
{
"version": 3,
"artifactType": {
"type": "AAPT_FRIENDLY_MERGED_MANIFESTS",
"kind": "Directory"
},
"applicationId": "com.github.barteksc.pdfviewer",
"variantName": "debug",
"elements": [
{
"type": "SINGLE",
"filters": [],
"attributes": [],
"outputFile": "AndroidManifest.xml"
}
],
"elementType": "File"
}
\ No newline at end of file
aarFormatVersion=1.0
aarMetadataVersion=1.0
minCompileSdk=1
minCompileSdkExtension=0
minAndroidGradlePluginVersion=1.0.0
coreLibraryDesugaringEnabled=false
int attr sb_handlerColor 0x0
int attr sb_horizontal 0x0
int attr sb_indicatorColor 0x0
int attr sb_indicatorTextColor 0x0
int drawable default_scroll_handle_bottom 0x0
int drawable default_scroll_handle_left 0x0
int drawable default_scroll_handle_right 0x0
int drawable default_scroll_handle_top 0x0
int[] styleable ScrollBar { 0x0, 0x0, 0x0, 0x0 }
int styleable ScrollBar_sb_handlerColor 0
int styleable ScrollBar_sb_horizontal 1
int styleable ScrollBar_sb_indicatorColor 2
int styleable ScrollBar_sb_indicatorTextColor 3
#Fri Feb 21 07:23:49 GMT 2025
com.github.barteksc.pdfviewer.android-pdf-viewer-main-6\:/drawable/default_scroll_handle_bottom.xml=D\:\\zxhy\\aawhitepackage\\scanqrWhite2copy1\\android-pdf-viewer\\build\\intermediates\\packaged_res\\debug\\packageDebugResources\\drawable\\default_scroll_handle_bottom.xml
com.github.barteksc.pdfviewer.android-pdf-viewer-main-6\:/drawable/default_scroll_handle_left.xml=D\:\\zxhy\\aawhitepackage\\scanqrWhite2copy1\\android-pdf-viewer\\build\\intermediates\\packaged_res\\debug\\packageDebugResources\\drawable\\default_scroll_handle_left.xml
com.github.barteksc.pdfviewer.android-pdf-viewer-main-6\:/drawable/default_scroll_handle_right.xml=D\:\\zxhy\\aawhitepackage\\scanqrWhite2copy1\\android-pdf-viewer\\build\\intermediates\\packaged_res\\debug\\packageDebugResources\\drawable\\default_scroll_handle_right.xml
com.github.barteksc.pdfviewer.android-pdf-viewer-main-6\:/drawable/default_scroll_handle_top.xml=D\:\\zxhy\\aawhitepackage\\scanqrWhite2copy1\\android-pdf-viewer\\build\\intermediates\\packaged_res\\debug\\packageDebugResources\\drawable\\default_scroll_handle_top.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ScrollBar">
<attr format="color|reference" name="sb_handlerColor"/>
<attr format="color|reference" name="sb_indicatorColor"/>
<attr format="color|reference" name="sb_indicatorTextColor"/>
<attr format="boolean|reference" name="sb_horizontal"/>
</declare-styleable>
</resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<merger version="3"><dataSet aapt-namespace="http://schemas.android.com/apk/res-auto" config="main$Generated" generated="true" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="D:\zxhy\aawhitepackage\scanqrWhite2copy1\android-pdf-viewer\src\main\res"/></dataSet><dataSet aapt-namespace="http://schemas.android.com/apk/res-auto" config="main" generated-set="main$Generated" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="D:\zxhy\aawhitepackage\scanqrWhite2copy1\android-pdf-viewer\src\main\res"><file name="default_scroll_handle_bottom" path="D:\zxhy\aawhitepackage\scanqrWhite2copy1\android-pdf-viewer\src\main\res\drawable\default_scroll_handle_bottom.xml" qualifiers="" type="drawable"/><file name="default_scroll_handle_left" path="D:\zxhy\aawhitepackage\scanqrWhite2copy1\android-pdf-viewer\src\main\res\drawable\default_scroll_handle_left.xml" qualifiers="" type="drawable"/><file name="default_scroll_handle_right" path="D:\zxhy\aawhitepackage\scanqrWhite2copy1\android-pdf-viewer\src\main\res\drawable\default_scroll_handle_right.xml" qualifiers="" type="drawable"/><file name="default_scroll_handle_top" path="D:\zxhy\aawhitepackage\scanqrWhite2copy1\android-pdf-viewer\src\main\res\drawable\default_scroll_handle_top.xml" qualifiers="" type="drawable"/><file path="D:\zxhy\aawhitepackage\scanqrWhite2copy1\android-pdf-viewer\src\main\res\values\attrs.xml" qualifiers=""><declare-styleable name="ScrollBar">
<attr format="color|reference" name="sb_handlerColor"/>
<attr format="color|reference" name="sb_indicatorColor"/>
<attr format="color|reference" name="sb_indicatorTextColor"/>
<attr format="boolean|reference" name="sb_horizontal"/>
</declare-styleable></file></source></dataSet><dataSet aapt-namespace="http://schemas.android.com/apk/res-auto" config="debug$Generated" generated="true" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="D:\zxhy\aawhitepackage\scanqrWhite2copy1\android-pdf-viewer\src\debug\res"/></dataSet><dataSet aapt-namespace="http://schemas.android.com/apk/res-auto" config="debug" generated-set="debug$Generated" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="D:\zxhy\aawhitepackage\scanqrWhite2copy1\android-pdf-viewer\src\debug\res"/></dataSet><dataSet aapt-namespace="http://schemas.android.com/apk/res-auto" config="generated$Generated" generated="true" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="D:\zxhy\aawhitepackage\scanqrWhite2copy1\android-pdf-viewer\build\generated\res\resValues\debug"/></dataSet><dataSet aapt-namespace="http://schemas.android.com/apk/res-auto" config="generated" generated-set="generated$Generated" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="D:\zxhy\aawhitepackage\scanqrWhite2copy1\android-pdf-viewer\build\generated\res\resValues\debug"/></dataSet><mergedItems><configuration qualifiers=""><declare-styleable name="ScrollBar">
<attr format="color|reference" name="sb_handlerColor"/>
<attr format="color|reference" name="sb_indicatorColor"/>
<attr format="color|reference" name="sb_indicatorTextColor"/>
<attr format="boolean|reference" name="sb_horizontal"/>
</declare-styleable></configuration></mergedItems></merger>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<merger version="3"><dataSet config="main" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="D:\zxhy\aawhitepackage\scanqrWhite2copy1\android-pdf-viewer\src\main\jniLibs"/></dataSet><dataSet config="debug" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="D:\zxhy\aawhitepackage\scanqrWhite2copy1\android-pdf-viewer\src\debug\jniLibs"/></dataSet></merger>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<merger version="3"><dataSet config="main" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="D:\zxhy\aawhitepackage\scanqrWhite2copy1\android-pdf-viewer\src\main\shaders"/></dataSet><dataSet config="debug" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="D:\zxhy\aawhitepackage\scanqrWhite2copy1\android-pdf-viewer\src\debug\shaders"/></dataSet></merger>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<merger version="3"><dataSet config="main" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="D:\zxhy\aawhitepackage\scanqrWhite2copy1\android-pdf-viewer\src\main\assets"/></dataSet><dataSet config="debug" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="D:\zxhy\aawhitepackage\scanqrWhite2copy1\android-pdf-viewer\src\debug\assets"/></dataSet><dataSet config="generated" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="D:\zxhy\aawhitepackage\scanqrWhite2copy1\android-pdf-viewer\build\intermediates\shader_assets\debug\compileDebugShaders\out"/></dataSet></merger>
\ No newline at end of file
R_DEF: Internal format may change without notice
local
attr? sb_handlerColor
attr? sb_horizontal
attr? sb_indicatorColor
attr? sb_indicatorTextColor
drawable default_scroll_handle_bottom
drawable default_scroll_handle_left
drawable default_scroll_handle_right
drawable default_scroll_handle_top
styleable ScrollBar sb_handlerColor sb_indicatorColor sb_indicatorTextColor sb_horizontal
1<?xml version="1.0" encoding="utf-8"?>
2<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3 package="com.github.barteksc.pdfviewer" >
4
5 <uses-sdk android:minSdkVersion="21" />
6
7</manifest>
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.github.barteksc.pdfviewer" >
<uses-sdk android:minSdkVersion="21" />
</manifest>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<stroke
android:color="#6C7A89"
android:width="1dp" />
<corners
android:topLeftRadius="10dp"
android:topRightRadius="10dp" />
<solid android:color="#DADFE1" />
</shape>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/default_scroll_handle_right"
android:fromDegrees="180"
android:toDegrees="180"
android:visible="true" />
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<stroke
android:color="#6C7A89"
android:width="1dp" />
<corners
android:bottomLeftRadius="10dp"
android:topLeftRadius="10dp" />
<solid android:color="#DADFE1" />
</shape>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/default_scroll_handle_bottom"
android:fromDegrees="180"
android:toDegrees="180"
android:visible="true" />
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ScrollBar">
<attr format="color|reference" name="sb_handlerColor"/>
<attr format="color|reference" name="sb_indicatorColor"/>
<attr format="color|reference" name="sb_indicatorTextColor"/>
<attr format="boolean|reference" name="sb_horizontal"/>
</declare-styleable>
</resources>
\ No newline at end of file
com.github.barteksc.pdfviewer
attr sb_handlerColor
attr sb_horizontal
attr sb_indicatorColor
attr sb_indicatorTextColor
drawable default_scroll_handle_bottom
drawable default_scroll_handle_left
drawable default_scroll_handle_right
drawable default_scroll_handle_top
styleable ScrollBar sb_handlerColor sb_horizontal sb_indicatorColor sb_indicatorTextColor
-- Merging decision tree log ---
manifest
ADDED from D:\zxhy\aawhitepackage\scanqrWhite2copy1\android-pdf-viewer\src\main\AndroidManifest.xml:2:1-4:12
INJECTED from D:\zxhy\aawhitepackage\scanqrWhite2copy1\android-pdf-viewer\src\main\AndroidManifest.xml:2:1-4:12
package
INJECTED from D:\zxhy\aawhitepackage\scanqrWhite2copy1\android-pdf-viewer\src\main\AndroidManifest.xml
xmlns:android
ADDED from D:\zxhy\aawhitepackage\scanqrWhite2copy1\android-pdf-viewer\src\main\AndroidManifest.xml:2:11-69
uses-sdk
INJECTED from D:\zxhy\aawhitepackage\scanqrWhite2copy1\android-pdf-viewer\src\main\AndroidManifest.xml reason: use-sdk injection requested
INJECTED from D:\zxhy\aawhitepackage\scanqrWhite2copy1\android-pdf-viewer\src\main\AndroidManifest.xml
INJECTED from D:\zxhy\aawhitepackage\scanqrWhite2copy1\android-pdf-viewer\src\main\AndroidManifest.xml
android:targetSdkVersion
INJECTED from D:\zxhy\aawhitepackage\scanqrWhite2copy1\android-pdf-viewer\src\main\AndroidManifest.xml
android:minSdkVersion
INJECTED from D:\zxhy\aawhitepackage\scanqrWhite2copy1\android-pdf-viewer\src\main\AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>
\ No newline at end of file
/**
* Copyright 2016 Bartosz Schiller
* <p/>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p/>
* http://www.apache.org/licenses/LICENSE-2.0
* <p/>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.barteksc.pdfviewer;
import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.graphics.PointF;
import android.view.animation.DecelerateInterpolator;
import android.widget.OverScroller;
/**
* This manager is used by the PDFView to launch animations.
* It uses the ValueAnimator appeared in API 11 to start
* an animation, and call moveTo() on the PDFView as a result
* of each animation update.
*/
class AnimationManager {
private PDFView pdfView;
private ValueAnimator animation;
private OverScroller scroller;
private boolean flinging = false;
public AnimationManager(PDFView pdfView) {
this.pdfView = pdfView;
scroller = new OverScroller(pdfView.getContext());
}
public void startXAnimation(float xFrom, float xTo) {
stopAll();
animation = ValueAnimator.ofFloat(xFrom, xTo);
animation.setInterpolator(new DecelerateInterpolator());
animation.addUpdateListener(new XAnimation());
animation.setDuration(400);
animation.start();
}
public void startYAnimation(float yFrom, float yTo) {
stopAll();
animation = ValueAnimator.ofFloat(yFrom, yTo);
animation.setInterpolator(new DecelerateInterpolator());
animation.addUpdateListener(new YAnimation());
animation.setDuration(400);
animation.start();
}
public void startZoomAnimation(float centerX, float centerY, float zoomFrom, float zoomTo) {
stopAll();
animation = ValueAnimator.ofFloat(zoomFrom, zoomTo);
animation.setInterpolator(new DecelerateInterpolator());
ZoomAnimation zoomAnim = new ZoomAnimation(centerX, centerY);
animation.addUpdateListener(zoomAnim);
animation.addListener(zoomAnim);
animation.setDuration(400);
animation.start();
}
public void startFlingAnimation(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY) {
stopAll();
flinging = true;
scroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY);
}
void computeFling() {
if (scroller.computeScrollOffset()) {
pdfView.moveTo(scroller.getCurrX(), scroller.getCurrY());
pdfView.loadPageByOffset();
} else if(flinging) { // fling finished
flinging = false;
pdfView.loadPages();
hideHandle();
}
}
public void stopAll() {
if (animation != null) {
animation.cancel();
animation = null;
}
stopFling();
}
public void stopFling() {
flinging = false;
scroller.forceFinished(true);
}
class XAnimation implements AnimatorUpdateListener {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float offset = (Float) animation.getAnimatedValue();
pdfView.moveTo(offset, pdfView.getCurrentYOffset());
}
}
class YAnimation implements AnimatorUpdateListener {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float offset = (Float) animation.getAnimatedValue();
pdfView.moveTo(pdfView.getCurrentXOffset(), offset);
}
}
class ZoomAnimation implements AnimatorUpdateListener, AnimatorListener {
private final float centerX;
private final float centerY;
public ZoomAnimation(float centerX, float centerY) {
this.centerX = centerX;
this.centerY = centerY;
}
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float zoom = (Float) animation.getAnimatedValue();
pdfView.zoomCenteredTo(zoom, new PointF(centerX, centerY));
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
pdfView.loadPages();
hideHandle();
}
@Override
public void onAnimationRepeat(Animator animation) {
}
@Override
public void onAnimationStart(Animator animation) {
}
}
private void hideHandle() {
if (pdfView.getScrollHandle() != null) {
pdfView.getScrollHandle().hideDelayed();
}
}
}
/**
* Copyright 2016 Bartosz Schiller
* <p/>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p/>
* http://www.apache.org/licenses/LICENSE-2.0
* <p/>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.barteksc.pdfviewer;
import android.graphics.RectF;
import com.github.barteksc.pdfviewer.model.PagePart;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.PriorityQueue;
import static com.github.barteksc.pdfviewer.util.Constants.Cache.CACHE_SIZE;
import static com.github.barteksc.pdfviewer.util.Constants.Cache.THUMBNAILS_CACHE_SIZE;
import org.jetbrains.annotations.Nullable;
class CacheManager {
private final PriorityQueue<PagePart> passiveCache;
private final PriorityQueue<PagePart> activeCache;
private final List<PagePart> thumbnails;
private final Object passiveActiveLock = new Object();
private final PagePartComparator comparator = new PagePartComparator();
public CacheManager() {
activeCache = new PriorityQueue<>(CACHE_SIZE, comparator);
passiveCache = new PriorityQueue<>(CACHE_SIZE, comparator);
thumbnails = new ArrayList<>();
}
public void cachePart(PagePart part) {
synchronized (passiveActiveLock) {
// If cache too big, remove and recycle
makeAFreeSpace();
// Then add part
activeCache.offer(part);
}
}
public void makeANewSet() {
synchronized (passiveActiveLock) {
passiveCache.addAll(activeCache);
activeCache.clear();
}
}
private void makeAFreeSpace() {
synchronized (passiveActiveLock) {
while ((activeCache.size() + passiveCache.size()) >= CACHE_SIZE &&
!passiveCache.isEmpty()) {
PagePart part = passiveCache.poll();
part.getRenderedBitmap().recycle();
}
while ((activeCache.size() + passiveCache.size()) >= CACHE_SIZE &&
!activeCache.isEmpty()) {
activeCache.poll().getRenderedBitmap().recycle();
}
}
}
public void cacheThumbnail(PagePart part) {
synchronized (thumbnails) {
// If cache too big, remove and recycle
if (thumbnails.size() >= THUMBNAILS_CACHE_SIZE) {
thumbnails.remove(0).getRenderedBitmap().recycle();
}
// Then add thumbnail
thumbnails.add(part);
}
}
public boolean upPartIfContained(int userPage, int page, float width, float height, RectF pageRelativeBounds, int toOrder) {
PagePart fakePart = new PagePart(userPage, page, null, width, height, pageRelativeBounds, false, 0);
PagePart found;
synchronized (passiveActiveLock) {
if ((found = find(passiveCache, fakePart)) != null) {
passiveCache.remove(found);
found.setCacheOrder(toOrder);
activeCache.offer(found);
return true;
}
return find(activeCache, fakePart) != null;
}
}
/**
* Return true if already contains the described PagePart
*/
public boolean containsThumbnail(int userPage, int page, float width, float height, RectF pageRelativeBounds) {
PagePart fakePart = new PagePart(userPage, page, null, width, height, pageRelativeBounds, true, 0);
synchronized (thumbnails) {
for (PagePart part : thumbnails) {
if (part.equals(fakePart)) {
return true;
}
}
return false;
}
}
@Nullable
private static PagePart find(PriorityQueue<PagePart> vector, PagePart fakePart) {
for (PagePart part : vector) {
if (part.equals(fakePart)) {
return part;
}
}
return null;
}
public List<PagePart> getPageParts() {
synchronized (passiveActiveLock) {
List<PagePart> parts = new ArrayList<>(passiveCache);
parts.addAll(activeCache);
return parts;
}
}
public List<PagePart> getThumbnails() {
synchronized (thumbnails) {
return thumbnails;
}
}
public void recycle() {
synchronized (passiveActiveLock) {
for (PagePart part : passiveCache) {
part.getRenderedBitmap().recycle();
}
passiveCache.clear();
for (PagePart part : activeCache) {
part.getRenderedBitmap().recycle();
}
activeCache.clear();
}
synchronized (thumbnails) {
for (PagePart part : thumbnails) {
part.getRenderedBitmap().recycle();
}
thumbnails.clear();
}
}
class PagePartComparator implements Comparator<PagePart> {
@Override
public int compare(PagePart part1, PagePart part2) {
if (part1.getCacheOrder() == part2.getCacheOrder()) {
return 0;
}
return part1.getCacheOrder() > part2.getCacheOrder() ? 1 : -1;
}
}
}
/**
* Copyright 2016 Bartosz Schiller
* <p/>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p/>
* http://www.apache.org/licenses/LICENSE-2.0
* <p/>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.barteksc.pdfviewer;
import android.content.Context;
import android.os.AsyncTask;
import com.github.barteksc.pdfviewer.source.DocumentSource;
import com.shockwave.pdfium.PdfDocument;
import com.shockwave.pdfium.PdfiumCore;
class DecodingAsyncTask extends AsyncTask<Void, Void, Throwable> {
private boolean cancelled;
private PDFView pdfView;
private Context context;
private PdfiumCore pdfiumCore;
private PdfDocument pdfDocument;
private String password;
private DocumentSource docSource;
private int firstPageIdx;
private int pageWidth;
private int pageHeight;
DecodingAsyncTask(DocumentSource docSource, String password, PDFView pdfView, PdfiumCore pdfiumCore, int firstPageIdx) {
this.docSource = docSource;
this.firstPageIdx = firstPageIdx;
this.cancelled = false;
this.pdfView = pdfView;
this.password = password;
this.pdfiumCore = pdfiumCore;
context = pdfView.getContext();
}
@Override
protected Throwable doInBackground(Void... params) {
try {
pdfDocument = docSource.createDocument(context, pdfiumCore, password);
// We assume all the pages are the same size
pdfiumCore.openPage(pdfDocument, firstPageIdx);
pageWidth = pdfiumCore.getPageWidth(pdfDocument, firstPageIdx);
pageHeight = pdfiumCore.getPageHeight(pdfDocument, firstPageIdx);
return null;
} catch (Throwable t) {
return t;
}
}
@Override
protected void onPostExecute(Throwable t) {
if (t != null) {
pdfView.loadError(t);
return;
}
if (!cancelled) {
pdfView.loadComplete(pdfDocument, pageWidth, pageHeight);
}
}
@Override
protected void onCancelled() {
cancelled = true;
}
}
/**
* Copyright 2016 Bartosz Schiller
* <p/>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p/>
* http://www.apache.org/licenses/LICENSE-2.0
* <p/>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.barteksc.pdfviewer;
import android.graphics.PointF;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import com.github.barteksc.pdfviewer.scroll.ScrollHandle;
import com.github.barteksc.pdfviewer.listener.OnTapListener;
import static com.github.barteksc.pdfviewer.util.Constants.Pinch.MAXIMUM_ZOOM;
import static com.github.barteksc.pdfviewer.util.Constants.Pinch.MINIMUM_ZOOM;
/**
* This Manager takes care of moving the PDFView,
* set its zoom track user actions.
*/
class DragPinchManager implements GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener, ScaleGestureDetector.OnScaleGestureListener, View.OnTouchListener {
private PDFView pdfView;
private AnimationManager animationManager;
private GestureDetector gestureDetector;
private ScaleGestureDetector scaleGestureDetector;
private boolean isSwipeEnabled;
private boolean swipeVertical;
private boolean scrolling = false;
private boolean scaling = false;
public DragPinchManager(PDFView pdfView, AnimationManager animationManager) {
this.pdfView = pdfView;
this.animationManager = animationManager;
this.isSwipeEnabled = false;
this.swipeVertical = pdfView.isSwipeVertical();
gestureDetector = new GestureDetector(pdfView.getContext(), this);
scaleGestureDetector = new ScaleGestureDetector(pdfView.getContext(), this);
pdfView.setOnTouchListener(this);
}
public void enableDoubletap(boolean enableDoubletap) {
if (enableDoubletap) {
gestureDetector.setOnDoubleTapListener(this);
} else {
gestureDetector.setOnDoubleTapListener(null);
}
}
public boolean isZooming() {
return pdfView.isZooming();
}
private boolean isPageChange(float distance) {
return Math.abs(distance) > Math.abs(pdfView.toCurrentScale(swipeVertical ? pdfView.getOptimalPageHeight() : pdfView.getOptimalPageWidth()) / 2);
}
public void setSwipeEnabled(boolean isSwipeEnabled) {
this.isSwipeEnabled = isSwipeEnabled;
}
public void setSwipeVertical(boolean swipeVertical) {
this.swipeVertical = swipeVertical;
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
OnTapListener onTapListener = pdfView.getOnTapListener();
if (onTapListener == null || !onTapListener.onTap(e)) {
ScrollHandle ps = pdfView.getScrollHandle();
if (ps != null && !pdfView.documentFitsView()) {
if (!ps.shown()) {
ps.show();
} else {
ps.hide();
}
}
}
pdfView.performClick();
return true;
}
@Override
public boolean onDoubleTap(MotionEvent e) {
if (pdfView.getZoom() < pdfView.getMidZoom()) {
pdfView.zoomWithAnimation(e.getX(), e.getY(), pdfView.getMidZoom());
} else if (pdfView.getZoom() < pdfView.getMaxZoom()) {
pdfView.zoomWithAnimation(e.getX(), e.getY(), pdfView.getMaxZoom());
} else {
pdfView.resetZoomWithAnimation();
}
return true;
}
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
return false;
}
@Override
public boolean onDown(MotionEvent e) {
animationManager.stopFling();
return true;
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
scrolling = true;
if (isZooming() || isSwipeEnabled) {
pdfView.moveRelativeTo(-distanceX, -distanceY);
}
if (!scaling || pdfView.doRenderDuringScale()) {
pdfView.loadPageByOffset();
}
return true;
}
public void onScrollEnd(MotionEvent event) {
pdfView.loadPages();
hideHandle();
}
@Override
public void onLongPress(MotionEvent e) {
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
int xOffset = (int) pdfView.getCurrentXOffset();
int yOffset = (int) pdfView.getCurrentYOffset();
float minX, minY;
if (pdfView.isSwipeVertical()) {
minX = -(pdfView.toCurrentScale(pdfView.getOptimalPageWidth()) - pdfView.getWidth());
minY = -(pdfView.calculateDocLength() - pdfView.getHeight());
} else {
minX = -(pdfView.calculateDocLength() - pdfView.getWidth());
minY = -(pdfView.toCurrentScale(pdfView.getOptimalPageHeight()) - pdfView.getHeight());
}
animationManager.startFlingAnimation(xOffset, yOffset, (int) (velocityX), (int) (velocityY),
(int) minX, 0, (int) minY, 0);
return true;
}
@Override
public boolean onScale(ScaleGestureDetector detector) {
float dr = detector.getScaleFactor();
float wantedZoom = pdfView.getZoom() * dr;
if (wantedZoom < MINIMUM_ZOOM) {
dr = MINIMUM_ZOOM / pdfView.getZoom();
} else if (wantedZoom > MAXIMUM_ZOOM) {
dr = MAXIMUM_ZOOM / pdfView.getZoom();
}
pdfView.zoomCenteredRelativeTo(dr, new PointF(detector.getFocusX(), detector.getFocusY()));
return true;
}
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
scaling = true;
return true;
}
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
pdfView.loadPages();
hideHandle();
scaling = false;
}
@Override
public boolean onTouch(View v, MotionEvent event) {
boolean retVal = scaleGestureDetector.onTouchEvent(event);
retVal = gestureDetector.onTouchEvent(event) || retVal;
if (event.getAction() == MotionEvent.ACTION_UP) {
if (scrolling) {
scrolling = false;
onScrollEnd(event);
}
}
return retVal;
}
private void hideHandle() {
if (pdfView.getScrollHandle() != null && pdfView.getScrollHandle().shown()) {
pdfView.getScrollHandle().hideDelayed();
}
}
}
/**
* Copyright 2016 Bartosz Schiller
* <p/>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p/>
* http://www.apache.org/licenses/LICENSE-2.0
* <p/>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.barteksc.pdfviewer;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.PaintFlagsDrawFilter;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.HandlerThread;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.RelativeLayout;
import com.github.barteksc.pdfviewer.exception.PageRenderingException;
import com.github.barteksc.pdfviewer.listener.OnDrawListener;
import com.github.barteksc.pdfviewer.listener.OnErrorListener;
import com.github.barteksc.pdfviewer.listener.OnLoadCompleteListener;
import com.github.barteksc.pdfviewer.listener.OnPageChangeListener;
import com.github.barteksc.pdfviewer.listener.OnPageErrorListener;
import com.github.barteksc.pdfviewer.listener.OnPageScrollListener;
import com.github.barteksc.pdfviewer.listener.OnRenderListener;
import com.github.barteksc.pdfviewer.listener.OnTapListener;
import com.github.barteksc.pdfviewer.model.PagePart;
import com.github.barteksc.pdfviewer.scroll.ScrollHandle;
import com.github.barteksc.pdfviewer.source.AssetSource;
import com.github.barteksc.pdfviewer.source.ByteArraySource;
import com.github.barteksc.pdfviewer.source.DocumentSource;
import com.github.barteksc.pdfviewer.source.FileSource;
import com.github.barteksc.pdfviewer.source.InputStreamSource;
import com.github.barteksc.pdfviewer.source.UriSource;
import com.github.barteksc.pdfviewer.util.ArrayUtils;
import com.github.barteksc.pdfviewer.util.Constants;
import com.github.barteksc.pdfviewer.util.MathUtils;
import com.github.barteksc.pdfviewer.util.Util;
import com.shockwave.pdfium.PdfDocument;
import com.shockwave.pdfium.PdfiumCore;
import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
/**
* It supports animations, zoom, cache, and swipe.
* <p>
* To fully understand this class you must know its principles :
* - The PDF document is seen as if we always want to draw all the pages.
* - The thing is that we only draw the visible parts.
* - All parts are the same size, this is because we can't interrupt a native page rendering,
* so we need these renderings to be as fast as possible, and be able to interrupt them
* as soon as we can.
* - The parts are loaded when the current offset or the current zoom level changes
* <p>
* Important :
* - DocumentPage = A page of the PDF document.
* - UserPage = A page as defined by the user.
* By default, they're the same. But the user can change the pages order
* using {@link #load(DocumentSource, String, OnLoadCompleteListener, OnErrorListener, int[])}. In this
* particular case, a userPage of 5 can refer to a documentPage of 17.
*/
public class PDFView extends RelativeLayout {
private static final String TAG = PDFView.class.getSimpleName();
public static final float DEFAULT_MAX_SCALE = 3.0f;
public static final float DEFAULT_MID_SCALE = 1.75f;
public static final float DEFAULT_MIN_SCALE = 1.0f;
private float minZoom = DEFAULT_MIN_SCALE;
private float midZoom = DEFAULT_MID_SCALE;
private float maxZoom = DEFAULT_MAX_SCALE;
/**
* START - scrolling in first page direction
* END - scrolling in last page direction
* NONE - not scrolling
*/
enum ScrollDir {
NONE, START, END
}
private ScrollDir scrollDir = ScrollDir.NONE;
/**
* Rendered parts go to the cache manager
*/
CacheManager cacheManager;
/**
* Animation manager manage all offset and zoom animation
*/
private AnimationManager animationManager;
/**
* Drag manager manage all touch events
*/
private DragPinchManager dragPinchManager;
/**
* The pages the user want to display in order
* (ex: 0, 2, 2, 8, 8, 1, 1, 1)
*/
private int[] originalUserPages;
/**
* The same pages but with a filter to avoid repetition
* (ex: 0, 2, 8, 1)
*/
private int[] filteredUserPages;
/**
* The same pages but with a filter to avoid repetition
* (ex: 0, 1, 1, 2, 2, 3, 3, 3)
*/
private int[] filteredUserPageIndexes;
/**
* Number of pages in the loaded PDF document
*/
private int documentPageCount;
/**
* The index of the current sequence
*/
private int currentPage;
/**
* The index of the current sequence
*/
private int currentFilteredPage;
/**
* The actual width and height of the pages in the PDF document
*/
private int pageWidth, pageHeight;
/**
* The optimal width and height of the pages to fit the component size
*/
private float optimalPageWidth, optimalPageHeight;
/**
* If you picture all the pages side by side in their optimal width,
* and taking into account the zoom level, the current offset is the
* position of the left border of the screen in this big picture
*/
private float currentXOffset = 0;
/**
* If you picture all the pages side by side in their optimal width,
* and taking into account the zoom level, the current offset is the
* position of the left border of the screen in this big picture
*/
private float currentYOffset = 0;
/**
* The zoom level, always >= 1
*/
private float zoom = 1f;
/**
* True if the PDFView has been recycled
*/
private boolean recycled = true;
/**
* Current state of the view
*/
private State state = State.DEFAULT;
/**
* Async task used during the loading phase to decode a PDF document
*/
private DecodingAsyncTask decodingAsyncTask;
/**
* The thread {@link #renderingHandler} will run on
*/
private final HandlerThread renderingHandlerThread;
/**
* Handler always waiting in the background and rendering tasks
*/
RenderingHandler renderingHandler;
private PagesLoader pagesLoader;
/**
* Call back object to call when the PDF is loaded
*/
private OnLoadCompleteListener onLoadCompleteListener;
private OnErrorListener onErrorListener;
/**
* Call back object to call when the page has changed
*/
private OnPageChangeListener onPageChangeListener;
/**
* Call back object to call when the page is scrolled
*/
private OnPageScrollListener onPageScrollListener;
/**
* Call back object to call when the above layer is to drawn
*/
private OnDrawListener onDrawListener;
private OnDrawListener onDrawAllListener;
/**
* Call back object to call when the document is initially rendered
*/
private OnRenderListener onRenderListener;
/**
* Call back object to call when the user does a tap gesture
*/
private OnTapListener onTapListener;
/**
* Call back object to call when the page load error occurs
*/
private OnPageErrorListener onPageErrorListener;
/**
* Paint object for drawing
*/
private Paint paint;
/**
* Paint object for drawing debug stuff
*/
private Paint debugPaint;
/**
* Paint used for invalid pages
*/
private int invalidPageColor = Color.WHITE;
private int defaultPage = 0;
/**
* True if should scroll through pages vertically instead of horizontally
*/
private boolean swipeVertical = true;
/**
* Pdfium core for loading and rendering PDFs
*/
private PdfiumCore pdfiumCore;
private PdfDocument pdfDocument;
private ScrollHandle scrollHandle;
private boolean isScrollHandleInit = false;
ScrollHandle getScrollHandle() {
return scrollHandle;
}
/**
* True if bitmap should use ARGB_8888 format and take more memory
* False if bitmap should be compressed by using RGB_565 format and take less memory
*/
private boolean bestQuality = false;
/**
* True if annotations should be rendered
* False otherwise
*/
private boolean annotationRendering = false;
/**
* True if the view should render during scaling<br/>
* Can not be forced on older API versions (< Build.VERSION_CODES.KITKAT) as the GestureDetector does
* not detect scrolling while scaling.<br/>
* False otherwise
*/
private boolean renderDuringScale = false;
/**
* Antialiasing and bitmap filtering
*/
private boolean enableAntialiasing = true;
private PaintFlagsDrawFilter antialiasFilter =
new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
/**
* Spacing between pages, in DP
*/
private int spacingPx = 0;
/**
* pages numbers used when calling onDrawAllListener
*/
private List<Integer> onDrawPagesNums = new ArrayList<>(10);
/**
* Construct the initial view
*/
public PDFView(Context context, AttributeSet set) {
super(context, set);
renderingHandlerThread = new HandlerThread("PDF renderer");
if (isInEditMode()) {
return;
}
cacheManager = new CacheManager();
animationManager = new AnimationManager(this);
dragPinchManager = new DragPinchManager(this, animationManager);
paint = new Paint();
debugPaint = new Paint();
debugPaint.setStyle(Style.STROKE);
pdfiumCore = new PdfiumCore(context);
setWillNotDraw(false);
}
private void load(DocumentSource docSource, String password, OnLoadCompleteListener listener, OnErrorListener onErrorListener) {
load(docSource, password, listener, onErrorListener, null);
}
private void load(DocumentSource docSource, String password, OnLoadCompleteListener onLoadCompleteListener, OnErrorListener onErrorListener, int[] userPages) {
if (!recycled) {
throw new IllegalStateException("Don't call load on a PDF View without recycling it first.");
}
// Manage UserPages if not null
if (userPages != null) {
this.originalUserPages = userPages;
this.filteredUserPages = ArrayUtils.deleteDuplicatedPages(originalUserPages);
this.filteredUserPageIndexes = ArrayUtils.calculateIndexesInDuplicateArray(originalUserPages);
}
this.onLoadCompleteListener = onLoadCompleteListener;
this.onErrorListener = onErrorListener;
int firstPageIdx = 0;
if (originalUserPages != null) {
firstPageIdx = originalUserPages[0];
}
recycled = false;
// Start decoding document
decodingAsyncTask = new DecodingAsyncTask(docSource, password, this, pdfiumCore, firstPageIdx);
decodingAsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
/**
* Go to the given page.
*
* @param page Page index.
*/
public void jumpTo(int page, boolean withAnimation) {
float offset = -calculatePageOffset(page);
if (swipeVertical) {
if (withAnimation) {
animationManager.startYAnimation(currentYOffset, offset);
} else {
moveTo(currentXOffset, offset);
}
} else {
if (withAnimation) {
animationManager.startXAnimation(currentXOffset, offset);
} else {
moveTo(offset, currentYOffset);
}
}
showPage(page);
}
public void jumpTo(int page) {
jumpTo(page, false);
}
void showPage(int pageNb) {
if (recycled) {
return;
}
// Check the page number and makes the
// difference between UserPages and DocumentPages
pageNb = determineValidPageNumberFrom(pageNb);
currentPage = pageNb;
currentFilteredPage = pageNb;
if (filteredUserPageIndexes != null) {
if (pageNb >= 0 && pageNb < filteredUserPageIndexes.length) {
pageNb = filteredUserPageIndexes[pageNb];
currentFilteredPage = pageNb;
}
}
loadPages();
if (scrollHandle != null && !documentFitsView()) {
scrollHandle.setPageNum(currentPage + 1);
}
if (onPageChangeListener != null) {
onPageChangeListener.onPageChanged(currentPage, getPageCount());
}
}
/**
* Get current position as ratio of document length to visible area.
* 0 means that document start is visible, 1 that document end is visible
*
* @return offset between 0 and 1
*/
public float getPositionOffset() {
float offset;
if (swipeVertical) {
offset = -currentYOffset / (calculateDocLength() - getHeight());
} else {
offset = -currentXOffset / (calculateDocLength() - getWidth());
}
return MathUtils.limit(offset, 0, 1);
}
/**
* @param progress must be between 0 and 1
* @param moveHandle whether to move scroll handle
* @see PDFView#getPositionOffset()
*/
public void setPositionOffset(float progress, boolean moveHandle) {
if (swipeVertical) {
moveTo(currentXOffset, (-calculateDocLength() + getHeight()) * progress, moveHandle);
} else {
moveTo((-calculateDocLength() + getWidth()) * progress, currentYOffset, moveHandle);
}
loadPageByOffset();
}
public void setPositionOffset(float progress) {
setPositionOffset(progress, true);
}
private float calculatePageOffset(int page) {
if (swipeVertical) {
return toCurrentScale(page * optimalPageHeight + page * spacingPx);
} else {
return toCurrentScale(page * optimalPageWidth + page * spacingPx);
}
}
float calculateDocLength() {
int pageCount = getPageCount();
if (swipeVertical) {
return toCurrentScale(pageCount * optimalPageHeight + (pageCount - 1) * spacingPx);
} else {
return toCurrentScale(pageCount * optimalPageWidth + (pageCount - 1) * spacingPx);
}
}
public void stopFling() {
animationManager.stopFling();
}
public int getPageCount() {
if (originalUserPages != null) {
return originalUserPages.length;
}
return documentPageCount;
}
public void enableSwipe(boolean enableSwipe) {
dragPinchManager.setSwipeEnabled(enableSwipe);
}
public void enableDoubletap(boolean enableDoubletap) {
this.dragPinchManager.enableDoubletap(enableDoubletap);
}
private void setOnPageChangeListener(OnPageChangeListener onPageChangeListener) {
this.onPageChangeListener = onPageChangeListener;
}
OnPageChangeListener getOnPageChangeListener() {
return this.onPageChangeListener;
}
private void setOnPageScrollListener(OnPageScrollListener onPageScrollListener) {
this.onPageScrollListener = onPageScrollListener;
}
OnPageScrollListener getOnPageScrollListener() {
return this.onPageScrollListener;
}
private void setOnRenderListener(OnRenderListener onRenderListener) {
this.onRenderListener = onRenderListener;
}
OnRenderListener getOnRenderListener() {
return this.onRenderListener;
}
private void setOnTapListener(OnTapListener onTapListener) {
this.onTapListener = onTapListener;
}
OnTapListener getOnTapListener() {
return this.onTapListener;
}
private void setOnDrawListener(OnDrawListener onDrawListener) {
this.onDrawListener = onDrawListener;
}
private void setOnDrawAllListener(OnDrawListener onDrawAllListener) {
this.onDrawAllListener = onDrawAllListener;
}
private void setOnPageErrorListener(OnPageErrorListener onPageErrorListener) {
this.onPageErrorListener = onPageErrorListener;
}
void onPageError(PageRenderingException ex) {
if (onPageErrorListener != null) {
onPageErrorListener.onPageError(ex.getPage(), ex.getCause());
} else {
Log.e(TAG, "Cannot open page " + ex.getPage(), ex.getCause());
}
}
public void recycle() {
animationManager.stopAll();
// Stop tasks
if (renderingHandler != null) {
renderingHandler.stop();
renderingHandler.removeMessages(RenderingHandler.MSG_RENDER_TASK);
}
if (decodingAsyncTask != null) {
decodingAsyncTask.cancel(true);
}
// Clear caches
cacheManager.recycle();
if (scrollHandle != null && isScrollHandleInit) {
scrollHandle.destroyLayout();
}
if (pdfiumCore != null && pdfDocument != null) {
pdfiumCore.closeDocument(pdfDocument);
}
renderingHandler = null;
originalUserPages = null;
filteredUserPages = null;
filteredUserPageIndexes = null;
pdfDocument = null;
scrollHandle = null;
isScrollHandleInit = false;
currentXOffset = currentYOffset = 0;
zoom = 1f;
recycled = true;
state = State.DEFAULT;
}
public boolean isRecycled() {
return recycled;
}
/**
* Handle fling animation
*/
@Override
public void computeScroll() {
super.computeScroll();
if (isInEditMode()) {
return;
}
animationManager.computeFling();
}
@Override
protected void onDetachedFromWindow() {
recycle();
super.onDetachedFromWindow();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
if (isInEditMode() || state != State.SHOWN) {
return;
}
animationManager.stopAll();
calculateOptimalWidthAndHeight();
if (swipeVertical) {
moveTo(currentXOffset, -calculatePageOffset(currentPage));
} else {
moveTo(-calculatePageOffset(currentPage), currentYOffset);
}
loadPageByOffset();
}
@Override
public boolean canScrollHorizontally(int direction) {
if (swipeVertical) {
if (direction < 0 && currentXOffset < 0) {
return true;
} else if (direction > 0 && currentXOffset + toCurrentScale(optimalPageWidth) > getWidth()) {
return true;
}
} else {
if (direction < 0 && currentXOffset < 0) {
return true;
} else if (direction > 0 && currentXOffset + calculateDocLength() > getWidth()) {
return true;
}
}
return false;
}
@Override
public boolean canScrollVertically(int direction) {
if (swipeVertical) {
if (direction < 0 && currentYOffset < 0) {
return true;
} else if (direction > 0 && currentYOffset + calculateDocLength() > getHeight()) {
return true;
}
} else {
if (direction < 0 && currentYOffset < 0) {
return true;
} else if (direction > 0 && currentYOffset + toCurrentScale(optimalPageHeight) > getHeight()) {
return true;
}
}
return false;
}
@Override
protected void onDraw(Canvas canvas) {
if (isInEditMode()) {
return;
}
// As I said in this class javadoc, we can think of this canvas as a huge
// strip on which we draw all the images. We actually only draw the rendered
// parts, of course, but we render them in the place they belong in this huge
// strip.
// That's where Canvas.translate(x, y) becomes very helpful.
// This is the situation :
// _______________________________________________
// | | |
// | the actual | The big strip |
// | canvas | |
// |_____________| |
// |_______________________________________________|
//
// If the rendered part is on the bottom right corner of the strip
// we can draw it but we won't see it because the canvas is not big enough.
// But if we call translate(-X, -Y) on the canvas just before drawing the object :
// _______________________________________________
// | _____________|
// | The big strip | |
// | | the actual |
// | | canvas |
// |_________________________________|_____________|
//
// The object will be on the canvas.
// This technique is massively used in this method, and allows
// abstraction of the screen position when rendering the parts.
// Draws background
if (enableAntialiasing) {
canvas.setDrawFilter(antialiasFilter);
}
Drawable bg = getBackground();
if (bg == null) {
canvas.drawColor(Color.WHITE);
} else {
bg.draw(canvas);
}
if (recycled) {
return;
}
if (state != State.SHOWN) {
return;
}
// Moves the canvas before drawing any element
float currentXOffset = this.currentXOffset;
float currentYOffset = this.currentYOffset;
canvas.translate(currentXOffset, currentYOffset);
// Draws thumbnails
for (PagePart part : cacheManager.getThumbnails()) {
drawPart(canvas, part);
}
// Draws parts
for (PagePart part : cacheManager.getPageParts()) {
drawPart(canvas, part);
if (onDrawAllListener != null && !onDrawPagesNums.contains(part.getUserPage())) {
onDrawPagesNums.add(part.getUserPage());
}
}
for (Integer page : onDrawPagesNums) {
drawWithListener(canvas, page, onDrawAllListener);
}
onDrawPagesNums.clear();
drawWithListener(canvas, currentPage, onDrawListener);
// Restores the canvas position
canvas.translate(-currentXOffset, -currentYOffset);
}
private void drawWithListener(Canvas canvas, int page, OnDrawListener listener) {
if (listener != null) {
float translateX, translateY;
if (swipeVertical) {
translateX = 0;
translateY = calculatePageOffset(page);
} else {
translateY = 0;
translateX = calculatePageOffset(page);
}
canvas.translate(translateX, translateY);
listener.onLayerDrawn(canvas,
toCurrentScale(optimalPageWidth),
toCurrentScale(optimalPageHeight),
page);
canvas.translate(-translateX, -translateY);
}
}
/**
* Draw a given PagePart on the canvas
*/
private void drawPart(Canvas canvas, PagePart part) {
// Can seem strange, but avoid lot of calls
RectF pageRelativeBounds = part.getPageRelativeBounds();
Bitmap renderedBitmap = part.getRenderedBitmap();
if (renderedBitmap.isRecycled()) {
return;
}
// Move to the target page
float localTranslationX = 0;
float localTranslationY = 0;
if (swipeVertical) {
localTranslationY = calculatePageOffset(part.getUserPage());
} else {
localTranslationX = calculatePageOffset(part.getUserPage());
}
canvas.translate(localTranslationX, localTranslationY);
Rect srcRect = new Rect(0, 0, renderedBitmap.getWidth(),
renderedBitmap.getHeight());
float offsetX = toCurrentScale(pageRelativeBounds.left * optimalPageWidth);
float offsetY = toCurrentScale(pageRelativeBounds.top * optimalPageHeight);
float width = toCurrentScale(pageRelativeBounds.width() * optimalPageWidth);
float height = toCurrentScale(pageRelativeBounds.height() * optimalPageHeight);
// If we use float values for this rectangle, there will be
// a possible gap between page parts, especially when
// the zoom level is high.
RectF dstRect = new RectF((int) offsetX, (int) offsetY,
(int) (offsetX + width),
(int) (offsetY + height));
// Check if bitmap is in the screen
float translationX = currentXOffset + localTranslationX;
float translationY = currentYOffset + localTranslationY;
if (translationX + dstRect.left >= getWidth() || translationX + dstRect.right <= 0 ||
translationY + dstRect.top >= getHeight() || translationY + dstRect.bottom <= 0) {
canvas.translate(-localTranslationX, -localTranslationY);
return;
}
canvas.drawBitmap(renderedBitmap, srcRect, dstRect, paint);
if (Constants.DEBUG_MODE) {
debugPaint.setColor(part.getUserPage() % 2 == 0 ? Color.RED : Color.BLUE);
canvas.drawRect(dstRect, debugPaint);
}
// Restore the canvas position
canvas.translate(-localTranslationX, -localTranslationY);
}
/**
* Load all the parts around the center of the screen,
* taking into account X and Y offsets, zoom level, and
* the current page displayed
*/
public void loadPages() {
if (optimalPageWidth == 0 || optimalPageHeight == 0 || renderingHandler == null) {
return;
}
// Cancel all current tasks
renderingHandler.removeMessages(RenderingHandler.MSG_RENDER_TASK);
cacheManager.makeANewSet();
pagesLoader.loadPages();
redraw();
}
/**
* Called when the PDF is loaded
*/
void loadComplete(PdfDocument pdfDocument, int pageWidth, int pageHeight) {
state = State.LOADED;
this.documentPageCount = pdfiumCore.getPageCount(pdfDocument);
this.pdfDocument = pdfDocument;
this.pageWidth = pageWidth;
this.pageHeight = pageHeight;
calculateOptimalWidthAndHeight();
pagesLoader = new PagesLoader(this);
if (!renderingHandlerThread.isAlive()) {
renderingHandlerThread.start();
}
renderingHandler = new RenderingHandler(renderingHandlerThread.getLooper(),
this, pdfiumCore, pdfDocument);
renderingHandler.start();
if (scrollHandle != null) {
scrollHandle.setupLayout(this);
isScrollHandleInit = true;
}
if (onLoadCompleteListener != null) {
onLoadCompleteListener.loadComplete(documentPageCount);
}
jumpTo(defaultPage, false);
}
void loadError(Throwable t) {
state = State.ERROR;
recycle();
invalidate();
if (this.onErrorListener != null) {
this.onErrorListener.onError(t);
} else {
Log.e("PDFView", "load pdf error", t);
}
}
void redraw() {
invalidate();
}
/**
* Called when a rendering task is over and
* a PagePart has been freshly created.
*
* @param part The created PagePart.
*/
public void onBitmapRendered(PagePart part) {
// when it is first rendered part
if (state == State.LOADED) {
state = State.SHOWN;
if (onRenderListener != null) {
onRenderListener.onInitiallyRendered(getPageCount(), optimalPageWidth, optimalPageHeight);
}
}
if (part.isThumbnail()) {
cacheManager.cacheThumbnail(part);
} else {
cacheManager.cachePart(part);
}
redraw();
}
/**
* Given the UserPage number, this method restrict it
* to be sure it's an existing page. It takes care of
* using the user defined pages if any.
*
* @param userPage A page number.
* @return A restricted valid page number (example : -2 => 0)
*/
private int determineValidPageNumberFrom(int userPage) {
if (userPage <= 0) {
return 0;
}
if (originalUserPages != null) {
if (userPage >= originalUserPages.length) {
return originalUserPages.length - 1;
}
} else {
if (userPage >= documentPageCount) {
return documentPageCount - 1;
}
}
return userPage;
}
/**
* Calculate the x/y-offset needed to have the given
* page centered on the screen. It doesn't take into
* account the zoom level.
*
* @param pageNb The page number.
* @return The x/y-offset to use to have the pageNb centered.
*/
private float calculateCenterOffsetForPage(int pageNb) {
if (swipeVertical) {
float imageY = -(pageNb * optimalPageHeight + pageNb * spacingPx);
imageY += getHeight() / 2 - optimalPageHeight / 2;
return imageY;
} else {
float imageX = -(pageNb * optimalPageWidth + pageNb * spacingPx);
imageX += getWidth() / 2 - optimalPageWidth / 2;
return imageX;
}
}
/**
* Calculate the optimal width and height of a page
* considering the area width and height
*/
private void calculateOptimalWidthAndHeight() {
if (state == State.DEFAULT || getWidth() == 0) {
return;
}
float maxWidth = getWidth(), maxHeight = getHeight();
float w = pageWidth, h = pageHeight;
float ratio = w / h;
w = maxWidth;
h = (float) Math.floor(maxWidth / ratio);
if (h > maxHeight) {
h = maxHeight;
w = (float) Math.floor(maxHeight * ratio);
}
optimalPageWidth = w;
optimalPageHeight = h;
}
public void moveTo(float offsetX, float offsetY) {
moveTo(offsetX, offsetY, true);
}
/**
* Move to the given X and Y offsets, but check them ahead of time
* to be sure not to go outside the the big strip.
*
* @param offsetX The big strip X offset to use as the left border of the screen.
* @param offsetY The big strip Y offset to use as the right border of the screen.
* @param moveHandle whether to move scroll handle or not
*/
public void moveTo(float offsetX, float offsetY, boolean moveHandle) {
if (swipeVertical) {
// Check X offset
float scaledPageWidth = toCurrentScale(optimalPageWidth);
if (scaledPageWidth < getWidth()) {
offsetX = getWidth() / 2 - scaledPageWidth / 2;
} else {
if (offsetX > 0) {
offsetX = 0;
} else if (offsetX + scaledPageWidth < getWidth()) {
offsetX = getWidth() - scaledPageWidth;
}
}
// Check Y offset
float contentHeight = calculateDocLength();
if (contentHeight < getHeight()) { // whole document height visible on screen
offsetY = (getHeight() - contentHeight) / 2;
} else {
if (offsetY > 0) { // top visible
offsetY = 0;
} else if (offsetY + contentHeight < getHeight()) { // bottom visible
offsetY = -contentHeight + getHeight();
}
}
if (offsetY < currentYOffset) {
scrollDir = ScrollDir.END;
} else if (offsetY > currentYOffset) {
scrollDir = ScrollDir.START;
} else {
scrollDir = ScrollDir.NONE;
}
} else {
// Check Y offset
float scaledPageHeight = toCurrentScale(optimalPageHeight);
if (scaledPageHeight < getHeight()) {
offsetY = getHeight() / 2 - scaledPageHeight / 2;
} else {
if (offsetY > 0) {
offsetY = 0;
} else if (offsetY + scaledPageHeight < getHeight()) {
offsetY = getHeight() - scaledPageHeight;
}
}
// Check X offset
float contentWidth = calculateDocLength();
if (contentWidth < getWidth()) { // whole document width visible on screen
offsetX = (getWidth() - contentWidth) / 2;
} else {
if (offsetX > 0) { // left visible
offsetX = 0;
} else if (offsetX + contentWidth < getWidth()) { // right visible
offsetX = -contentWidth + getWidth();
}
}
if (offsetX < currentXOffset) {
scrollDir = ScrollDir.END;
} else if (offsetX > currentXOffset) {
scrollDir = ScrollDir.START;
} else {
scrollDir = ScrollDir.NONE;
}
}
currentXOffset = offsetX;
currentYOffset = offsetY;
float positionOffset = getPositionOffset();
if (moveHandle && scrollHandle != null && !documentFitsView()) {
scrollHandle.setScroll(positionOffset);
}
if (onPageScrollListener != null) {
onPageScrollListener.onPageScrolled(getCurrentPage(), positionOffset);
}
redraw();
}
ScrollDir getScrollDir() {
return scrollDir;
}
void loadPageByOffset() {
if (0 == getPageCount()) {
return;
}
float offset, optimal, screenCenter;
float spacingPerPage = spacingPx - (spacingPx / getPageCount());
if (swipeVertical) {
offset = currentYOffset;
optimal = optimalPageHeight + spacingPerPage;
screenCenter = ((float) getHeight()) / 2;
} else {
offset = currentXOffset;
optimal = optimalPageWidth + spacingPerPage;
screenCenter = ((float) getWidth()) / 2;
}
int page = (int) Math.floor((Math.abs(offset) + screenCenter) / toCurrentScale(optimal));
if (page >= 0 && page <= getPageCount() - 1 && page != getCurrentPage()) {
showPage(page);
} else {
loadPages();
}
}
int[] getFilteredUserPages() {
return filteredUserPages;
}
int[] getOriginalUserPages() {
return originalUserPages;
}
int[] getFilteredUserPageIndexes() {
return filteredUserPageIndexes;
}
int getDocumentPageCount() {
return documentPageCount;
}
/**
* Move relatively to the current position.
*
* @param dx The X difference you want to apply.
* @param dy The Y difference you want to apply.
* @see #moveTo(float, float)
*/
public void moveRelativeTo(float dx, float dy) {
moveTo(currentXOffset + dx, currentYOffset + dy);
}
/**
* Change the zoom level
*/
public void zoomTo(float zoom) {
this.zoom = zoom;
}
/**
* Change the zoom level, relatively to a pivot point.
* It will call moveTo() to make sure the given point stays
* in the middle of the screen.
*
* @param zoom The zoom level.
* @param pivot The point on the screen that should stays.
*/
public void zoomCenteredTo(float zoom, PointF pivot) {
float dzoom = zoom / this.zoom;
zoomTo(zoom);
float baseX = currentXOffset * dzoom;
float baseY = currentYOffset * dzoom;
baseX += (pivot.x - pivot.x * dzoom);
baseY += (pivot.y - pivot.y * dzoom);
moveTo(baseX, baseY);
}
/**
* @see #zoomCenteredTo(float, PointF)
*/
public void zoomCenteredRelativeTo(float dzoom, PointF pivot) {
zoomCenteredTo(zoom * dzoom, pivot);
}
/**
* Checks if whole document can be displayed on screen, doesn't include zoom
*
* @return true if whole document can displayed at once, false otherwise
*/
public boolean documentFitsView() {
int pageCount = getPageCount();
int spacing = (pageCount - 1) * spacingPx;
if (swipeVertical) {
return pageCount * optimalPageHeight + spacing < getHeight();
} else {
return pageCount * optimalPageWidth + spacing < getWidth();
}
}
public void fitToWidth(int page) {
if (state != State.SHOWN) {
Log.e(TAG, "Cannot fit, document not rendered yet");
return;
}
fitToWidth();
jumpTo(page);
}
public void fitToWidth() {
if (state != State.SHOWN) {
Log.e(TAG, "Cannot fit, document not rendered yet");
return;
}
zoomTo(getWidth() / optimalPageWidth);
setPositionOffset(0);
}
public int getCurrentPage() {
return currentPage;
}
public float getCurrentXOffset() {
return currentXOffset;
}
public float getCurrentYOffset() {
return currentYOffset;
}
public float toRealScale(float size) {
return size / zoom;
}
public float toCurrentScale(float size) {
return size * zoom;
}
public float getZoom() {
return zoom;
}
public boolean isZooming() {
return zoom != minZoom;
}
public float getOptimalPageWidth() {
return optimalPageWidth;
}
public float getOptimalPageHeight() {
return optimalPageHeight;
}
private void setDefaultPage(int defaultPage) {
this.defaultPage = defaultPage;
}
public void resetZoom() {
zoomTo(minZoom);
}
public void resetZoomWithAnimation() {
zoomWithAnimation(minZoom);
}
public void zoomWithAnimation(float centerX, float centerY, float scale) {
animationManager.startZoomAnimation(centerX, centerY, zoom, scale);
}
public void zoomWithAnimation(float scale) {
animationManager.startZoomAnimation(getWidth() / 2, getHeight() / 2, zoom, scale);
}
private void setScrollHandle(ScrollHandle scrollHandle) {
this.scrollHandle = scrollHandle;
}
/**
* Get page number at given offset
*
* @param positionOffset scroll offset between 0 and 1
* @return page number at given offset, starting from 0
*/
public int getPageAtPositionOffset(float positionOffset) {
int page = (int) Math.floor(getPageCount() * positionOffset);
return page == getPageCount() ? page - 1 : page;
}
public float getMinZoom() {
return minZoom;
}
public void setMinZoom(float minZoom) {
this.minZoom = minZoom;
}
public float getMidZoom() {
return midZoom;
}
public void setMidZoom(float midZoom) {
this.midZoom = midZoom;
}
public float getMaxZoom() {
return maxZoom;
}
public void setMaxZoom(float maxZoom) {
this.maxZoom = maxZoom;
}
public void useBestQuality(boolean bestQuality) {
this.bestQuality = bestQuality;
}
public boolean isBestQuality() {
return bestQuality;
}
public boolean isSwipeVertical() {
return swipeVertical;
}
public void setSwipeVertical(boolean swipeVertical) {
this.swipeVertical = swipeVertical;
}
public void enableAnnotationRendering(boolean annotationRendering) {
this.annotationRendering = annotationRendering;
}
public boolean isAnnotationRendering() {
return annotationRendering;
}
public void enableRenderDuringScale(boolean renderDuringScale) {
this.renderDuringScale = renderDuringScale;
}
public boolean isAntialiasing() {
return enableAntialiasing;
}
public void enableAntialiasing(boolean enableAntialiasing) {
this.enableAntialiasing = enableAntialiasing;
}
int getSpacingPx() {
return spacingPx;
}
private void setSpacing(int spacing) {
this.spacingPx = Util.getDP(getContext(), spacing);
}
private void setInvalidPageColor(int invalidPageColor) {
this.invalidPageColor = invalidPageColor;
}
public int getInvalidPageColor() {
return invalidPageColor;
}
public boolean doRenderDuringScale() {
return renderDuringScale;
}
public PdfDocument.Meta getDocumentMeta() {
if (pdfDocument == null) {
return null;
}
return pdfiumCore.getDocumentMeta(pdfDocument);
}
public List<PdfDocument.Bookmark> getTableOfContents() {
if (pdfDocument == null) {
return new ArrayList<>();
}
return pdfiumCore.getTableOfContents(pdfDocument);
}
/**
* Use an asset file as the pdf source
*/
public Configurator fromAsset(String assetName) {
return new Configurator(new AssetSource(assetName));
}
/**
* Use a file as the pdf source
*/
public Configurator fromFile(File file) {
return new Configurator(new FileSource(file));
}
/**
* Use URI as the pdf source, for use with content providers
*/
public Configurator fromUri(Uri uri) {
return new Configurator(new UriSource(uri));
}
/**
* Use bytearray as the pdf source, documents is not saved
*
* @param bytes
* @return
*/
public Configurator fromBytes(byte[] bytes) {
return new Configurator(new ByteArraySource(bytes));
}
public Configurator fromStream(InputStream stream) {
return new Configurator(new InputStreamSource(stream));
}
/**
* Use custom source as pdf source
*/
public Configurator fromSource(DocumentSource docSource) {
return new Configurator(docSource);
}
private enum State {DEFAULT, LOADED, SHOWN, ERROR}
public class Configurator {
private final DocumentSource documentSource;
private int[] pageNumbers = null;
private boolean enableSwipe = true;
private boolean enableDoubletap = true;
private OnDrawListener onDrawListener;
private OnDrawListener onDrawAllListener;
private OnLoadCompleteListener onLoadCompleteListener;
private OnErrorListener onErrorListener;
private OnPageChangeListener onPageChangeListener;
private OnPageScrollListener onPageScrollListener;
private OnRenderListener onRenderListener;
private OnTapListener onTapListener;
private OnPageErrorListener onPageErrorListener;
private int defaultPage = 0;
private boolean swipeHorizontal = false;
private boolean annotationRendering = false;
private String password = null;
private ScrollHandle scrollHandle = null;
private boolean antialiasing = true;
private int spacing = 0;
private int invalidPageColor = Color.WHITE;
private Configurator(DocumentSource documentSource) {
this.documentSource = documentSource;
}
public Configurator pages(int... pageNumbers) {
this.pageNumbers = pageNumbers;
return this;
}
public Configurator enableSwipe(boolean enableSwipe) {
this.enableSwipe = enableSwipe;
return this;
}
public Configurator enableDoubletap(boolean enableDoubletap) {
this.enableDoubletap = enableDoubletap;
return this;
}
public Configurator enableAnnotationRendering(boolean annotationRendering) {
this.annotationRendering = annotationRendering;
return this;
}
public Configurator onDraw(OnDrawListener onDrawListener) {
this.onDrawListener = onDrawListener;
return this;
}
public Configurator onDrawAll(OnDrawListener onDrawAllListener) {
this.onDrawAllListener = onDrawAllListener;
return this;
}
public Configurator onLoad(OnLoadCompleteListener onLoadCompleteListener) {
this.onLoadCompleteListener = onLoadCompleteListener;
return this;
}
public Configurator onPageScroll(OnPageScrollListener onPageScrollListener) {
this.onPageScrollListener = onPageScrollListener;
return this;
}
public Configurator onError(OnErrorListener onErrorListener) {
this.onErrorListener = onErrorListener;
return this;
}
public Configurator onPageError(OnPageErrorListener onPageErrorListener) {
this.onPageErrorListener = onPageErrorListener;
return this;
}
public Configurator onPageChange(OnPageChangeListener onPageChangeListener) {
this.onPageChangeListener = onPageChangeListener;
return this;
}
public Configurator onRender(OnRenderListener onRenderListener) {
this.onRenderListener = onRenderListener;
return this;
}
public Configurator onTap(OnTapListener onTapListener) {
this.onTapListener = onTapListener;
return this;
}
public Configurator defaultPage(int defaultPage) {
this.defaultPage = defaultPage;
return this;
}
public Configurator swipeHorizontal(boolean swipeHorizontal) {
this.swipeHorizontal = swipeHorizontal;
return this;
}
public Configurator password(String password) {
this.password = password;
return this;
}
public Configurator scrollHandle(ScrollHandle scrollHandle) {
this.scrollHandle = scrollHandle;
return this;
}
public Configurator enableAntialiasing(boolean antialiasing) {
this.antialiasing = antialiasing;
return this;
}
public Configurator spacing(int spacing) {
this.spacing = spacing;
return this;
}
public Configurator invalidPageColor(int invalidPageColor) {
this.invalidPageColor = invalidPageColor;
return this;
}
public void load() {
PDFView.this.recycle();
PDFView.this.setOnDrawListener(onDrawListener);
PDFView.this.setOnDrawAllListener(onDrawAllListener);
PDFView.this.setOnPageChangeListener(onPageChangeListener);
PDFView.this.setOnPageScrollListener(onPageScrollListener);
PDFView.this.setOnRenderListener(onRenderListener);
PDFView.this.setOnTapListener(onTapListener);
PDFView.this.setOnPageErrorListener(onPageErrorListener);
PDFView.this.enableSwipe(enableSwipe);
PDFView.this.enableDoubletap(enableDoubletap);
PDFView.this.setDefaultPage(defaultPage);
PDFView.this.setSwipeVertical(!swipeHorizontal);
PDFView.this.enableAnnotationRendering(annotationRendering);
PDFView.this.setScrollHandle(scrollHandle);
PDFView.this.enableAntialiasing(antialiasing);
PDFView.this.setSpacing(spacing);
PDFView.this.setInvalidPageColor(invalidPageColor);
PDFView.this.dragPinchManager.setSwipeVertical(swipeVertical);
PDFView.this.post(new Runnable() {
@Override
public void run() {
if (pageNumbers != null) {
PDFView.this.load(documentSource, password, onLoadCompleteListener, onErrorListener, pageNumbers);
} else {
PDFView.this.load(documentSource, password, onLoadCompleteListener, onErrorListener);
}
}
});
}
}
}
package com.github.barteksc.pdfviewer;
import android.graphics.RectF;
import android.util.Pair;
import com.github.barteksc.pdfviewer.util.Constants;
import com.github.barteksc.pdfviewer.util.MathUtils;
import static com.github.barteksc.pdfviewer.util.Constants.Cache.CACHE_SIZE;
class PagesLoader {
private PDFView pdfView;
// variables set on every call to loadPages()
private int cacheOrder;
private float scaledHeight;
private float scaledWidth;
private Pair<Integer, Integer> colsRows;
private float xOffset;
private float yOffset;
private float rowHeight;
private float colWidth;
private float pageRelativePartWidth;
private float pageRelativePartHeight;
private float partRenderWidth;
private float partRenderHeight;
private int thumbnailWidth;
private int thumbnailHeight;
private float scaledSpacingPx;
private final RectF thumbnailRect = new RectF(0, 0, 1, 1);
private class Holder {
int page;
int row;
int col;
}
PagesLoader(PDFView pdfView) {
this.pdfView = pdfView;
}
private Pair<Integer, Integer> getPageColsRows() {
float ratioX = 1f / pdfView.getOptimalPageWidth();
float ratioY = 1f / pdfView.getOptimalPageHeight();
final float partHeight = (Constants.PART_SIZE * ratioY) / pdfView.getZoom();
final float partWidth = (Constants.PART_SIZE * ratioX) / pdfView.getZoom();
final int nbRows = MathUtils.ceil(1f / partHeight);
final int nbCols = MathUtils.ceil(1f / partWidth);
return new Pair<>(nbCols, nbRows);
}
private int documentPage(int userPage) {
int documentPage = userPage;
if (pdfView.getOriginalUserPages() != null) {
if (userPage < 0 || userPage >= pdfView.getOriginalUserPages().length) {
return -1;
} else {
documentPage = pdfView.getOriginalUserPages()[userPage];
}
}
if (documentPage < 0 || userPage >= pdfView.getDocumentPageCount()) {
return -1;
}
return documentPage;
}
/**
* @param offset
* @param endOffset, if true, then rounding up, else rounding down
* @return
*/
private Holder getPageAndCoordsByOffset(float offset, boolean endOffset) {
Holder holder = new Holder();
float fixOffset = -MathUtils.max(offset, 0);
float row, col;
if (pdfView.isSwipeVertical()) {
holder.page = MathUtils.floor(fixOffset / (scaledHeight + scaledSpacingPx));
row = Math.abs(fixOffset - (scaledHeight + scaledSpacingPx) * holder.page) / rowHeight;
col = xOffset / colWidth;
} else {
holder.page = MathUtils.floor(fixOffset / (scaledWidth + scaledSpacingPx));
col = Math.abs(fixOffset - (scaledWidth + scaledSpacingPx) * holder.page) / colWidth;
row = yOffset / rowHeight;
}
if (endOffset) {
holder.row = MathUtils.ceil(row);
holder.col = MathUtils.ceil(col);
} else {
holder.row = MathUtils.floor(row);
holder.col = MathUtils.floor(col);
}
return holder;
}
private void loadThumbnail(int userPage, int documentPage) {
if (!pdfView.cacheManager.containsThumbnail(userPage, documentPage,
thumbnailWidth, thumbnailHeight, thumbnailRect)) {
pdfView.renderingHandler.addRenderingTask(userPage, documentPage,
thumbnailWidth, thumbnailHeight, thumbnailRect,
true, 0, pdfView.isBestQuality(), pdfView.isAnnotationRendering());
}
}
/**
* @param number if < 0 then row (column) is above view, else row (column) is visible or below view
* @return
*/
private int loadRelative(int number, int nbOfPartsLoadable, boolean belowView) {
int loaded = 0;
float newOffset;
if (pdfView.isSwipeVertical()) {
float rowsHeight = rowHeight * number + 1;
newOffset = pdfView.getCurrentYOffset() - (belowView ? pdfView.getHeight() : 0) - rowsHeight;
} else {
float colsWidth = colWidth * number;
newOffset = pdfView.getCurrentXOffset() - (belowView ? pdfView.getWidth() : 0) - colsWidth;
}
Holder holder = getPageAndCoordsByOffset(newOffset, false);
int documentPage = documentPage(holder.page);
if (documentPage < 0) {
return 0;
}
loadThumbnail(holder.page, documentPage);
if (pdfView.isSwipeVertical()) {
int firstCol = MathUtils.floor(xOffset / colWidth);
firstCol = MathUtils.min(firstCol - 1, 0);
int lastCol = MathUtils.ceil((xOffset + pdfView.getWidth()) / colWidth);
lastCol = MathUtils.max(lastCol + 1, colsRows.first);
for (int col = firstCol; col <= lastCol; col++) {
if (loadCell(holder.page, documentPage, holder.row, col, pageRelativePartWidth, pageRelativePartHeight)) {
loaded++;
}
if (loaded >= nbOfPartsLoadable) {
return loaded;
}
}
} else {
int firstRow = MathUtils.floor(yOffset / rowHeight);
firstRow = MathUtils.min(firstRow - 1, 0);
int lastRow = MathUtils.ceil((yOffset + pdfView.getHeight()) / rowHeight);
lastRow = MathUtils.max(lastRow + 1, colsRows.second);
for (int row = firstRow; row <= lastRow; row++) {
if (loadCell(holder.page, documentPage, row, holder.col, pageRelativePartWidth, pageRelativePartHeight)) {
loaded++;
}
if (loaded >= nbOfPartsLoadable) {
return loaded;
}
}
}
return loaded;
}
public int loadVisible() {
int parts = 0;
Holder firstHolder, lastHolder;
if (pdfView.isSwipeVertical()) {
firstHolder = getPageAndCoordsByOffset(pdfView.getCurrentYOffset(), false);
lastHolder = getPageAndCoordsByOffset(pdfView.getCurrentYOffset() - pdfView.getHeight() + 1, true);
int visibleRows = 0;
if (firstHolder.page == lastHolder.page) {
visibleRows = lastHolder.row - firstHolder.row + 1;
} else {
visibleRows += colsRows.second - firstHolder.row;
for (int page = firstHolder.page + 1; page < lastHolder.page; page++) {
visibleRows += colsRows.second;
}
visibleRows += lastHolder.row + 1;
}
for (int i = 0; i < visibleRows && parts < CACHE_SIZE; i++) {
parts += loadRelative(i, CACHE_SIZE - parts, false);
}
} else {
firstHolder = getPageAndCoordsByOffset(pdfView.getCurrentXOffset(), false);
lastHolder = getPageAndCoordsByOffset(pdfView.getCurrentXOffset() - pdfView.getWidth() + 1, true);
int visibleCols = 0;
if (firstHolder.page == lastHolder.page) {
visibleCols = lastHolder.col - firstHolder.col + 1;
} else {
visibleCols += colsRows.first - firstHolder.col;
for (int page = firstHolder.page + 1; page < lastHolder.page; page++) {
visibleCols += colsRows.first;
}
visibleCols += lastHolder.col + 1;
}
for (int i = 0; i < visibleCols && parts < CACHE_SIZE; i++) {
parts += loadRelative(i, CACHE_SIZE - parts, false);
}
}
int prevDocPage = documentPage(firstHolder.page - 1);
if (prevDocPage >= 0) {
loadThumbnail(firstHolder.page - 1, prevDocPage);
}
int nextDocPage = documentPage(firstHolder.page + 1);
if (nextDocPage >= 0) {
loadThumbnail(firstHolder.page + 1, nextDocPage);
}
return parts;
}
private boolean loadCell(int userPage, int documentPage, int row, int col, float pageRelativePartWidth, float pageRelativePartHeight) {
float relX = pageRelativePartWidth * col;
float relY = pageRelativePartHeight * row;
float relWidth = pageRelativePartWidth;
float relHeight = pageRelativePartHeight;
// Adjust width and height to
// avoid being outside the page
float renderWidth = partRenderWidth;
float renderHeight = partRenderHeight;
if (relX + relWidth > 1) {
relWidth = 1 - relX;
}
if (relY + relHeight > 1) {
relHeight = 1 - relY;
}
renderWidth *= relWidth;
renderHeight *= relHeight;
RectF pageRelativeBounds = new RectF(relX, relY, relX + relWidth, relY + relHeight);
if (renderWidth > 0 && renderHeight > 0) {
if (!pdfView.cacheManager.upPartIfContained(userPage, documentPage, renderWidth, renderHeight, pageRelativeBounds, cacheOrder)) {
pdfView.renderingHandler.addRenderingTask(userPage, documentPage,
renderWidth, renderHeight, pageRelativeBounds, false, cacheOrder,
pdfView.isBestQuality(), pdfView.isAnnotationRendering());
}
cacheOrder++;
return true;
}
return false;
}
public void loadPages() {
scaledHeight = pdfView.toCurrentScale(pdfView.getOptimalPageHeight());
scaledWidth = pdfView.toCurrentScale(pdfView.getOptimalPageWidth());
thumbnailWidth = (int) (pdfView.getOptimalPageWidth() * Constants.THUMBNAIL_RATIO);
thumbnailHeight = (int) (pdfView.getOptimalPageHeight() * Constants.THUMBNAIL_RATIO);
colsRows = getPageColsRows();
xOffset = -MathUtils.max(pdfView.getCurrentXOffset(), 0);
yOffset = -MathUtils.max(pdfView.getCurrentYOffset(), 0);
rowHeight = scaledHeight / colsRows.second;
colWidth = scaledWidth / colsRows.first;
pageRelativePartWidth = 1f / (float) colsRows.first;
pageRelativePartHeight = 1f / (float) colsRows.second;
partRenderWidth = Constants.PART_SIZE / pageRelativePartWidth;
partRenderHeight = Constants.PART_SIZE / pageRelativePartHeight;
cacheOrder = 1;
scaledSpacingPx = pdfView.toCurrentScale(pdfView.getSpacingPx());
scaledSpacingPx -= scaledSpacingPx / pdfView.getPageCount();
int loaded = loadVisible();
if (pdfView.getScrollDir().equals(PDFView.ScrollDir.END)) { // if scrolling to end, preload next view
for (int i = 0; i < Constants.PRELOAD_COUNT && loaded < CACHE_SIZE; i++) {
loaded += loadRelative(i, loaded, true);
}
} else { // if scrolling to start, preload previous view
for (int i = 0; i > -Constants.PRELOAD_COUNT && loaded < CACHE_SIZE; i--) {
loaded += loadRelative(i, loaded, false);
}
}
}
}
/**
* Copyright 2016 Bartosz Schiller
* <p/>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p/>
* http://www.apache.org/licenses/LICENSE-2.0
* <p/>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.barteksc.pdfviewer;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.SparseBooleanArray;
import com.github.barteksc.pdfviewer.exception.PageRenderingException;
import com.github.barteksc.pdfviewer.model.PagePart;
import com.shockwave.pdfium.PdfDocument;
import com.shockwave.pdfium.PdfiumCore;
/**
* A {@link Handler} that will process incoming {@link RenderingTask} messages
* and alert {@link PDFView#onBitmapRendered(PagePart)} when the portion of the
* PDF is ready to render.
*/
class RenderingHandler extends Handler {
/**
* {@link Message#what} kind of message this handler processes.
*/
static final int MSG_RENDER_TASK = 1;
private static final String TAG = RenderingHandler.class.getName();
private PdfiumCore pdfiumCore;
private PdfDocument pdfDocument;
private PDFView pdfView;
private RectF renderBounds = new RectF();
private Rect roundedRenderBounds = new Rect();
private Matrix renderMatrix = new Matrix();
private final SparseBooleanArray openedPages = new SparseBooleanArray();
private boolean running = false;
RenderingHandler(Looper looper, PDFView pdfView, PdfiumCore pdfiumCore, PdfDocument pdfDocument) {
super(looper);
this.pdfView = pdfView;
this.pdfiumCore = pdfiumCore;
this.pdfDocument = pdfDocument;
}
void addRenderingTask(int userPage, int page, float width, float height, RectF bounds, boolean thumbnail, int cacheOrder, boolean bestQuality, boolean annotationRendering) {
RenderingTask task = new RenderingTask(width, height, bounds, userPage, page, thumbnail, cacheOrder, bestQuality, annotationRendering);
Message msg = obtainMessage(MSG_RENDER_TASK, task);
sendMessage(msg);
}
@Override
public void handleMessage(Message message) {
RenderingTask task = (RenderingTask) message.obj;
try {
final PagePart part = proceed(task);
if (part != null) {
if (running) {
pdfView.post(new Runnable() {
@Override
public void run() {
pdfView.onBitmapRendered(part);
}
});
} else {
part.getRenderedBitmap().recycle();
}
}
} catch (final PageRenderingException ex) {
pdfView.post(new Runnable() {
@Override
public void run() {
pdfView.onPageError(ex);
}
});
}
}
private PagePart proceed(RenderingTask renderingTask) throws PageRenderingException {
if (openedPages.indexOfKey(renderingTask.page) < 0) {
try {
pdfiumCore.openPage(pdfDocument, renderingTask.page);
openedPages.put(renderingTask.page, true);
} catch (Exception e) {
openedPages.put(renderingTask.page, false);
throw new PageRenderingException(renderingTask.page, e);
}
}
int w = Math.round(renderingTask.width);
int h = Math.round(renderingTask.height);
Bitmap render;
try {
render = Bitmap.createBitmap(w, h, renderingTask.bestQuality ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565);
} catch (IllegalArgumentException e) {
e.printStackTrace();
return null;
}
calculateBounds(w, h, renderingTask.bounds);
if (openedPages.get(renderingTask.page)) {
pdfiumCore.renderPageBitmap(pdfDocument, render, renderingTask.page,
roundedRenderBounds.left, roundedRenderBounds.top,
roundedRenderBounds.width(), roundedRenderBounds.height(), renderingTask.annotationRendering);
} else {
render.eraseColor(pdfView.getInvalidPageColor());
}
return new PagePart(renderingTask.userPage, renderingTask.page, render,
renderingTask.width, renderingTask.height,
renderingTask.bounds, renderingTask.thumbnail,
renderingTask.cacheOrder);
}
private void calculateBounds(int width, int height, RectF pageSliceBounds) {
renderMatrix.reset();
renderMatrix.postTranslate(-pageSliceBounds.left * width, -pageSliceBounds.top * height);
renderMatrix.postScale(1 / pageSliceBounds.width(), 1 / pageSliceBounds.height());
renderBounds.set(0, 0, width, height);
renderMatrix.mapRect(renderBounds);
renderBounds.round(roundedRenderBounds);
}
void stop() {
running = false;
}
void start() {
running = true;
}
private class RenderingTask {
float width, height;
RectF bounds;
int page;
int userPage;
boolean thumbnail;
int cacheOrder;
boolean bestQuality;
boolean annotationRendering;
RenderingTask(float width, float height, RectF bounds, int userPage, int page, boolean thumbnail, int cacheOrder, boolean bestQuality, boolean annotationRendering) {
this.page = page;
this.width = width;
this.height = height;
this.bounds = bounds;
this.userPage = userPage;
this.thumbnail = thumbnail;
this.cacheOrder = cacheOrder;
this.bestQuality = bestQuality;
this.annotationRendering = annotationRendering;
}
}
}
/**
* Copyright 2016 Bartosz Schiller
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.barteksc.pdfviewer.exception;
@Deprecated
public class FileNotFoundException extends RuntimeException {
public FileNotFoundException(String detailMessage) {
super(detailMessage);
}
public FileNotFoundException(String detailMessage, Throwable throwable) {
super(detailMessage, throwable);
}
}
package com.github.barteksc.pdfviewer.exception;
public class PageRenderingException extends Exception {
private final int page;
public PageRenderingException(int page, Throwable cause) {
super(cause);
this.page = page;
}
public int getPage() {
return page;
}
}
/**
* Copyright 2016 Bartosz Schiller
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.barteksc.pdfviewer.listener;
import android.graphics.Canvas;
/**
* This interface allows an extern class to draw
* something on the PDFView canvas, above all images.
*/
public interface OnDrawListener {
/**
* This method is called when the PDFView is
* drawing its view.
* <p>
* The page is starting at (0,0)
*
* @param canvas The canvas on which to draw things.
* @param pageWidth The width of the current page.
* @param pageHeight The height of the current page.
* @param displayedPage The current page index
*/
void onLayerDrawn(Canvas canvas, float pageWidth, float pageHeight, int displayedPage);
}
/**
* Copyright 2016 Bartosz Schiller
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.barteksc.pdfviewer.listener;
public interface OnErrorListener {
/**
* Called if error occurred while opening PDF
* @param t Throwable with error
*/
void onError(Throwable t);
}
/**
* Copyright 2016 Bartosz Schiller
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.barteksc.pdfviewer.listener;
/**
* Implement this interface to receive events from PDFView
* when loading is complete.
*/
public interface OnLoadCompleteListener {
/**
* Called when the PDF is loaded
* @param nbPages the number of pages in this PDF file
*/
void loadComplete(int nbPages);
}
/**
* Copyright 2016 Bartosz Schiller
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.barteksc.pdfviewer.listener;
/**
* Implements this interface to receive events from PDFView
* when a page has changed through swipe
*/
public interface OnPageChangeListener {
/**
* Called when the user use swipe to change page
*
* @param page the new page displayed, starting from 0
* @param pageCount the total page count
*/
void onPageChanged(int page, int pageCount);
}
/**
* Copyright 2017 Bartosz Schiller
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.barteksc.pdfviewer.listener;
public interface OnPageErrorListener {
/**
* Called if error occurred while loading PDF page
* @param t Throwable with error
*/
void onPageError(int page, Throwable t);
}
/**
* Copyright 2016 Bartosz Schiller
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.barteksc.pdfviewer.listener;
/**
* Implements this interface to receive events from PDFView
* when a page has been scrolled
*/
public interface OnPageScrollListener {
/**
* Called on every move while scrolling
*
* @param page current page index
* @param positionOffset see {@link com.github.barteksc.pdfviewer.PDFView#getPositionOffset()}
*/
void onPageScrolled(int page, float positionOffset);
}
/**
* Copyright 2017 Bartosz Schiller
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.barteksc.pdfviewer.listener;
public interface OnRenderListener {
/**
* Called only once, when document is rendered
* @param nbPages number of pages
* @param pageWidth width of page
* @param pageHeight height of page
*/
void onInitiallyRendered(int nbPages, float pageWidth, float pageHeight);
}
/**
* Copyright 2017 Bartosz Schiller
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.barteksc.pdfviewer.listener;
import android.view.MotionEvent;
/**
* Implement this interface to receive events from PDFView
* when view has been touched
*/
public interface OnTapListener {
/**
* Called when the user has a tap gesture, before processing scroll handle toggling
*
* @param e MotionEvent that registered as a confirmed single tap
* @return true if the single tap was handled, false to toggle scroll handle
*/
boolean onTap(MotionEvent e);
}
/**
* Copyright 2016 Bartosz Schiller
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.barteksc.pdfviewer.model;
import android.graphics.Bitmap;
import android.graphics.RectF;
public class PagePart {
private int userPage;
private int page;
private Bitmap renderedBitmap;
private float width, height;
private RectF pageRelativeBounds;
private boolean thumbnail;
private int cacheOrder;
public PagePart(int userPage, int page, Bitmap renderedBitmap, float width, float height, RectF pageRelativeBounds, boolean thumbnail, int cacheOrder) {
super();
this.userPage = userPage;
this.page = page;
this.renderedBitmap = renderedBitmap;
this.pageRelativeBounds = pageRelativeBounds;
this.thumbnail = thumbnail;
this.cacheOrder = cacheOrder;
}
public int getCacheOrder() {
return cacheOrder;
}
public int getPage() {
return page;
}
public int getUserPage() {
return userPage;
}
public Bitmap getRenderedBitmap() {
return renderedBitmap;
}
public RectF getPageRelativeBounds() {
return pageRelativeBounds;
}
public float getWidth() {
return width;
}
public float getHeight() {
return height;
}
public boolean isThumbnail() {
return thumbnail;
}
public void setCacheOrder(int cacheOrder) {
this.cacheOrder = cacheOrder;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof PagePart)) {
return false;
}
PagePart part = (PagePart) obj;
return part.getPage() == page
&& part.getUserPage() == userPage
&& part.getWidth() == width
&& part.getHeight() == height
&& part.getPageRelativeBounds().left == pageRelativeBounds.left
&& part.getPageRelativeBounds().right == pageRelativeBounds.right
&& part.getPageRelativeBounds().top == pageRelativeBounds.top
&& part.getPageRelativeBounds().bottom == pageRelativeBounds.bottom;
}
}
package com.github.barteksc.pdfviewer.scroll;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.core.content.ContextCompat;
import com.github.barteksc.pdfviewer.PDFView;
import com.github.barteksc.pdfviewer.R;
import com.github.barteksc.pdfviewer.util.Util;
public class DefaultScrollHandle extends RelativeLayout implements ScrollHandle {
private final static int HANDLE_LONG = 65;
private final static int HANDLE_SHORT = 40;
private final static int DEFAULT_TEXT_SIZE = 16;
private float relativeHandlerMiddle = 0f;
protected TextView textView;
protected Context context;
private boolean inverted;
private PDFView pdfView;
private float currentPos;
private Handler handler = new Handler();
private Runnable hidePageScrollerRunnable = new Runnable() {
@Override
public void run() {
hide();
}
};
public DefaultScrollHandle(Context context) {
this(context, false);
}
public DefaultScrollHandle(Context context, boolean inverted) {
super(context);
this.context = context;
this.inverted = inverted;
textView = new TextView(context);
setVisibility(INVISIBLE);
setTextColor(Color.BLACK);
setTextSize(DEFAULT_TEXT_SIZE);
}
@Override
public void setupLayout(PDFView pdfView) {
int align, width, height;
Drawable background;
// determine handler position, default is right (when scrolling vertically) or bottom (when scrolling horizontally)
if (pdfView.isSwipeVertical()) {
width = HANDLE_LONG;
height = HANDLE_SHORT;
if (inverted) { // left
align = ALIGN_PARENT_LEFT;
background = ContextCompat.getDrawable(context, R.drawable.default_scroll_handle_left);
} else { // right
align = ALIGN_PARENT_RIGHT;
background = ContextCompat.getDrawable(context, R.drawable.default_scroll_handle_right);
}
} else {
width = HANDLE_SHORT;
height = HANDLE_LONG;
if (inverted) { // top
align = ALIGN_PARENT_TOP;
background = ContextCompat.getDrawable(context, R.drawable.default_scroll_handle_top);
} else { // bottom
align = ALIGN_PARENT_BOTTOM;
background = ContextCompat.getDrawable(context, R.drawable.default_scroll_handle_bottom);
}
}
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN) {
setBackgroundDrawable(background);
} else {
setBackground(background);
}
LayoutParams lp = new LayoutParams(Util.getDP(context, width), Util.getDP(context, height));
lp.setMargins(0, 0, 0, 0);
LayoutParams tvlp = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
tvlp.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE);
addView(textView, tvlp);
lp.addRule(align);
pdfView.addView(this, lp);
this.pdfView = pdfView;
}
@Override
public void destroyLayout() {
pdfView.removeView(this);
}
@Override
public void setScroll(float position) {
if (!shown()) {
show();
} else {
handler.removeCallbacks(hidePageScrollerRunnable);
}
setPosition((pdfView.isSwipeVertical() ? pdfView.getHeight() : pdfView.getWidth()) * position);
}
private void setPosition(float pos) {
if (Float.isInfinite(pos) || Float.isNaN(pos)) {
return;
}
float pdfViewSize;
if (pdfView.isSwipeVertical()) {
pdfViewSize = pdfView.getHeight();
} else {
pdfViewSize = pdfView.getWidth();
}
pos -= relativeHandlerMiddle;
if (pos < 0) {
pos = 0;
} else if (pos > pdfViewSize - Util.getDP(context, HANDLE_SHORT)) {
pos = pdfViewSize - Util.getDP(context, HANDLE_SHORT);
}
if (pdfView.isSwipeVertical()) {
setY(pos);
} else {
setX(pos);
}
calculateMiddle();
invalidate();
}
private void calculateMiddle() {
float pos, viewSize, pdfViewSize;
if (pdfView.isSwipeVertical()) {
pos = getY();
viewSize = getHeight();
pdfViewSize = pdfView.getHeight();
} else {
pos = getX();
viewSize = getWidth();
pdfViewSize = pdfView.getWidth();
}
relativeHandlerMiddle = ((pos + relativeHandlerMiddle) / pdfViewSize) * viewSize;
}
@Override
public void hideDelayed() {
handler.postDelayed(hidePageScrollerRunnable, 1000);
}
@Override
public void setPageNum(int pageNum) {
String text = String.valueOf(pageNum);
if (!textView.getText().equals(text)) {
textView.setText(text);
}
}
@Override
public boolean shown() {
return getVisibility() == VISIBLE;
}
@Override
public void show() {
setVisibility(VISIBLE);
}
@Override
public void hide() {
setVisibility(INVISIBLE);
}
public void setTextColor(int color) {
textView.setTextColor(color);
}
/**
* @param size text size in dp
*/
public void setTextSize(int size) {
textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, size);
}
private boolean isPDFViewReady() {
return pdfView != null && pdfView.getPageCount() > 0 && !pdfView.documentFitsView();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (!isPDFViewReady()) {
return super.onTouchEvent(event);
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
pdfView.stopFling();
handler.removeCallbacks(hidePageScrollerRunnable);
if (pdfView.isSwipeVertical()) {
currentPos = event.getRawY() - getY();
} else {
currentPos = event.getRawX() - getX();
}
case MotionEvent.ACTION_MOVE:
if (pdfView.isSwipeVertical()) {
setPosition(event.getRawY() - currentPos + relativeHandlerMiddle);
pdfView.setPositionOffset(relativeHandlerMiddle / (float) getHeight(), false);
} else {
setPosition(event.getRawX() - currentPos + relativeHandlerMiddle);
pdfView.setPositionOffset(relativeHandlerMiddle / (float) getWidth(), false);
}
return true;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
hideDelayed();
return true;
}
return super.onTouchEvent(event);
}
}
package com.github.barteksc.pdfviewer.scroll;
import com.github.barteksc.pdfviewer.PDFView;
public interface ScrollHandle {
/**
* Used to move the handle, called internally by PDFView
*
* @param position current scroll ratio between 0 and 1
*/
void setScroll(float position);
/**
* Method called by PDFView after setting scroll handle.
* Do not call this method manually.
* For usage sample see {@link DefaultScrollHandle}
*
* @param pdfView PDFView instance
*/
void setupLayout(PDFView pdfView);
/**
* Method called by PDFView when handle should be removed from layout
* Do not call this method manually.
*/
void destroyLayout();
/**
* Set page number displayed on handle
*
* @param pageNum page number
*/
void setPageNum(int pageNum);
/**
* Get handle visibility
*
* @return true if handle is visible, false otherwise
*/
boolean shown();
/**
* Show handle
*/
void show();
/**
* Hide handle immediately
*/
void hide();
/**
* Hide handle after some time (defined by implementation)
*/
void hideDelayed();
}
/*
* Copyright (C) 2016 Bartosz Schiller.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.barteksc.pdfviewer.source;
import android.content.Context;
import android.os.ParcelFileDescriptor;
import com.github.barteksc.pdfviewer.util.FileUtils;
import com.shockwave.pdfium.PdfDocument;
import com.shockwave.pdfium.PdfiumCore;
import java.io.File;
import java.io.IOException;
public class AssetSource implements DocumentSource {
private final String assetName;
public AssetSource(String assetName) {
this.assetName = assetName;
}
@Override
public PdfDocument createDocument(Context context, PdfiumCore core, String password) throws IOException {
File f = FileUtils.fileFromAsset(context, assetName);
ParcelFileDescriptor pfd = ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY);
return core.newDocument(pfd, password);
}
}
/*
* Copyright (C) 2016 Bartosz Schiller.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.barteksc.pdfviewer.source;
import android.content.Context;
import com.shockwave.pdfium.PdfDocument;
import com.shockwave.pdfium.PdfiumCore;
import java.io.IOException;
public class ByteArraySource implements DocumentSource {
private byte[] data;
public ByteArraySource(byte[] data) {
this.data = data;
}
@Override
public PdfDocument createDocument(Context context, PdfiumCore core, String password) throws IOException {
return core.newDocument(data, password);
}
}
/*
* Copyright (C) 2016 Bartosz Schiller.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.barteksc.pdfviewer.source;
import android.content.Context;
import com.shockwave.pdfium.PdfDocument;
import com.shockwave.pdfium.PdfiumCore;
import java.io.IOException;
public interface DocumentSource {
PdfDocument createDocument(Context context, PdfiumCore core, String password) throws IOException;
}
/*
* Copyright (C) 2016 Bartosz Schiller.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.barteksc.pdfviewer.source;
import android.content.Context;
import android.os.ParcelFileDescriptor;
import com.shockwave.pdfium.PdfDocument;
import com.shockwave.pdfium.PdfiumCore;
import java.io.File;
import java.io.IOException;
public class FileSource implements DocumentSource {
private File file;
public FileSource(File file) {
this.file = file;
}
@Override
public PdfDocument createDocument(Context context, PdfiumCore core, String password) throws IOException {
ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
return core.newDocument(pfd, password);
}
}
/*
* Copyright (C) 2016 Bartosz Schiller.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.barteksc.pdfviewer.source;
import android.content.Context;
import com.github.barteksc.pdfviewer.util.Util;
import com.shockwave.pdfium.PdfDocument;
import com.shockwave.pdfium.PdfiumCore;
import java.io.IOException;
import java.io.InputStream;
public class InputStreamSource implements DocumentSource {
private InputStream inputStream;
public InputStreamSource(InputStream inputStream) {
this.inputStream = inputStream;
}
@Override
public PdfDocument createDocument(Context context, PdfiumCore core, String password) throws IOException {
return core.newDocument(Util.toByteArray(inputStream), password);
}
}
/*
* Copyright (C) 2016 Bartosz Schiller.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.barteksc.pdfviewer.source;
import android.content.Context;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import com.shockwave.pdfium.PdfDocument;
import com.shockwave.pdfium.PdfiumCore;
import java.io.IOException;
public class UriSource implements DocumentSource {
private Uri uri;
public UriSource(Uri uri) {
this.uri = uri;
}
@Override
public PdfDocument createDocument(Context context, PdfiumCore core, String password) throws IOException {
ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(uri, "r");
return core.newDocument(pfd, password);
}
}
/**
* Copyright 2016 Bartosz Schiller
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.barteksc.pdfviewer.util;
import java.util.ArrayList;
import java.util.List;
public class ArrayUtils {
private ArrayUtils() {
// Prevents instantiation
}
/** Transforms (0,1,2,2,3) to (0,1,2,3) */
public static int[] deleteDuplicatedPages(int[] pages) {
List<Integer> result = new ArrayList<>();
int lastInt = -1;
for (Integer currentInt : pages) {
if (lastInt != currentInt) {
result.add(currentInt);
}
lastInt = currentInt;
}
int[] arrayResult = new int[result.size()];
for (int i = 0; i < result.size(); i++) {
arrayResult[i] = result.get(i);
}
return arrayResult;
}
/** Transforms (0, 4, 4, 6, 6, 6, 3) into (0, 1, 1, 2, 2, 2, 3) */
public static int[] calculateIndexesInDuplicateArray(int[] originalUserPages) {
int[] result = new int[originalUserPages.length];
if (originalUserPages.length == 0) {
return result;
}
int index = 0;
result[0] = index;
for (int i = 1; i < originalUserPages.length; i++) {
if (originalUserPages[i] != originalUserPages[i - 1]) {
index++;
}
result[i] = index;
}
return result;
}
public static String arrayToString(int[] array) {
StringBuilder builder = new StringBuilder("[");
for (int i = 0; i < array.length; i++) {
builder.append(array[i]);
if (i != array.length - 1) {
builder.append(",");
}
}
builder.append("]");
return builder.toString();
}
}
/**
* Copyright 2016 Bartosz Schiller
* <p/>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p/>
* http://www.apache.org/licenses/LICENSE-2.0
* <p/>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.barteksc.pdfviewer.util;
public class Constants {
public static boolean DEBUG_MODE = false;
/** Between 0 and 1, the thumbnails quality (default 0.3). Increasing this value may cause performance decrease */
public static float THUMBNAIL_RATIO = 0.3f;
/**
* The size of the rendered parts (default 256)
* Tinier : a little bit slower to have the whole page rendered but more reactive.
* Bigger : user will have to wait longer to have the first visual results
*/
public static float PART_SIZE = 256;
/** Number of preloaded rows or columns */
public static int PRELOAD_COUNT = 7;
public static class Cache {
/** The size of the cache (number of bitmaps kept) */
public static int CACHE_SIZE = 120;
public static int THUMBNAILS_CACHE_SIZE = 6;
}
public static class Pinch {
public static float MAXIMUM_ZOOM = 10;
public static float MINIMUM_ZOOM = 1;
}
}
/**
* Copyright 2016 Bartosz Schiller
* <p/>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p/>
* http://www.apache.org/licenses/LICENSE-2.0
* <p/>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.barteksc.pdfviewer.util;
import android.content.Context;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class FileUtils {
private FileUtils() {
// Prevents instantiation
}
public static File fileFromAsset(Context context, String assetName) throws IOException {
File outFile = new File(context.getCacheDir(), assetName + "-pdfview.pdf");
if (assetName.contains("/")) {
outFile.getParentFile().mkdirs();
}
copy(context.getAssets().open(assetName), outFile);
return outFile;
}
public static void copy(InputStream inputStream, File output) throws IOException {
OutputStream outputStream = null;
try {
outputStream = new FileOutputStream(output);
int read = 0;
byte[] bytes = new byte[1024];
while ((read = inputStream.read(bytes)) != -1) {
outputStream.write(bytes, 0, read);
}
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
} finally {
if (outputStream != null) {
outputStream.close();
}
}
}
}
}
/**
* Copyright 2016 Bartosz Schiller
* <p/>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p/>
* http://www.apache.org/licenses/LICENSE-2.0
* <p/>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.barteksc.pdfviewer.util;
public class MathUtils {
static private final int BIG_ENOUGH_INT = 16 * 1024;
static private final double BIG_ENOUGH_FLOOR = BIG_ENOUGH_INT;
static private final double BIG_ENOUGH_CEIL = 16384.999999999996;
private MathUtils() {
// Prevents instantiation
}
/**
* Limits the given <b>number</b> between the other values
* @param number The number to limit.
* @param between The smallest value the number can take.
* @param and The biggest value the number can take.
* @return The limited number.
*/
public static int limit(int number, int between, int and) {
if (number <= between) {
return between;
}
if (number >= and) {
return and;
}
return number;
}
/**
* Limits the given <b>number</b> between the other values
* @param number The number to limit.
* @param between The smallest value the number can take.
* @param and The biggest value the number can take.
* @return The limited number.
*/
public static float limit(float number, float between, float and) {
if (number <= between) {
return between;
}
if (number >= and) {
return and;
}
return number;
}
public static float max(float number, float max) {
if (number > max) {
return max;
}
return number;
}
public static float min(float number, float min) {
if (number < min) {
return min;
}
return number;
}
public static int max(int number, int max) {
if (number > max) {
return max;
}
return number;
}
public static int min(int number, int min) {
if (number < min) {
return min;
}
return number;
}
/**
* Methods from libGDX - https://github.com/libgdx/libgdx
*/
/** Returns the largest integer less than or equal to the specified float. This method will only properly floor floats from
* -(2^14) to (Float.MAX_VALUE - 2^14). */
static public int floor(float value) {
return (int) (value + BIG_ENOUGH_FLOOR) - BIG_ENOUGH_INT;
}
/** Returns the smallest integer greater than or equal to the specified float. This method will only properly ceil floats from
* -(2^14) to (Float.MAX_VALUE - 2^14). */
static public int ceil(float value) {
return (int) (value + BIG_ENOUGH_CEIL) - BIG_ENOUGH_INT;
}
}
/*
* Copyright (C) 2016 Bartosz Schiller.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.barteksc.pdfviewer.util;
import android.content.Context;
import android.util.TypedValue;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
public class Util {
private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
public static int getDP(Context context, int dp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, context.getResources().getDisplayMetrics());
}
public static byte[] toByteArray(InputStream inputStream) throws IOException {
ByteArrayOutputStream os = new ByteArrayOutputStream();
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
int n;
while (-1 != (n = inputStream.read(buffer))) {
os.write(buffer, 0, n);
}
return os.toByteArray();
}
}
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<stroke
android:color="#6C7A89"
android:width="1dp" />
<corners
android:topLeftRadius="10dp"
android:topRightRadius="10dp" />
<solid android:color="#DADFE1" />
</shape>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/default_scroll_handle_right"
android:fromDegrees="180"
android:toDegrees="180"
android:visible="true" />
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<stroke
android:color="#6C7A89"
android:width="1dp" />
<corners
android:bottomLeftRadius="10dp"
android:topLeftRadius="10dp" />
<solid android:color="#DADFE1" />
</shape>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/default_scroll_handle_bottom"
android:fromDegrees="180"
android:toDegrees="180"
android:visible="true" />
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ScrollBar">
<attr name="sb_handlerColor" format="color|reference" />
<attr name="sb_indicatorColor" format="color|reference" />
<attr name="sb_indicatorTextColor" format="color|reference" />
<attr name="sb_horizontal" format="boolean|reference" />
</declare-styleable>
</resources>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment