Android 原生PDF预览
实现思路过程
PdfRenderer用于将文件写入Bitmap
// create a new renderer
PdfRenderer renderer = new PdfRenderer(getSeekableFileDescriptor());
// let us just render all pages
final int pageCount = renderer.getPageCount();
for (int i = 0; i < pageCount; i++) {
Page page = renderer.openPage(i);
// say we render for showing on the screen
page.render(mBitmap, null, null, Page.RENDER_MODE_FOR_DISPLAY);
// do stuff with the bitmap
// close the page
page.close();
}
// close the renderer
renderer.close();
- 自定义
SurfaceView用于将Bitmap绘制到Canvas画布上
// 获取 SurfaceHolder 对象的锁,以进行图形操作
Canvas canvas = mSurfaceHolder.lockCanvas();
if (canvas != null) {
canvas.drawColor(Color.LTGRAY);
canvas.drawBitmap(mBitmap, 0, 0, null);
// 释放锁
mSurfaceHolder.unlockCanvasAndPost(canvas);
}
- 自定义
RecycleView控制多个SurfaceView展示多个页面 LinearSnapHelper关联RecycleView单个Item铺满一屏- 页面结束释放Bitmap
具体代码
- 自定义
SurfaceView
package com.example.tss.render;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
/**
* @author Administrator
* @date 2024/3/29 0029 10:11
* @description: MySurfaceView
*/
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback {
private SurfaceHolder mSurfaceHolder;
private boolean isSurfaceCreated;
public boolean isSurfaceCreated() {
return isSurfaceCreated;
}
public MySurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
// 获取 SurfaceHolder 对象,并设置回调
mSurfaceHolder = getHolder();
mSurfaceHolder.addCallback(this);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
isSurfaceCreated = true;
if (onAlreadyListener != null) {
Bitmap bitmap = onAlreadyListener.onAlready(holder);
// 在这里进行渲染操作
drawSurface(bitmap);
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
// 可以在这里进行 surface 大小变化时的操作
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
isSurfaceCreated = false;
if (onAlreadyListener != null) {
onAlreadyListener.onFinish(holder);
}
}
private void drawSurface(Bitmap mBitmap) {
// 获取 SurfaceHolder 对象的锁,以进行图形操作
Canvas canvas = mSurfaceHolder.lockCanvas();
if (canvas != null) {
canvas.drawColor(Color.LTGRAY);
canvas.drawBitmap(mBitmap, 0, 0, null);
// 释放锁
mSurfaceHolder.unlockCanvasAndPost(canvas);
}
}
private OnAlreadyListener onAlreadyListener;
public void setOnAlreadyListener(OnAlreadyListener onAlreadyListener) {
this.onAlreadyListener = onAlreadyListener;
}
public interface OnAlreadyListener {
Bitmap onAlready(SurfaceHolder holder);
void onFinish(SurfaceHolder holder);
}
}
- 创建
item_render_pdf.xml
<?xml version="1.0" encoding="utf-8"?>
<com.example.tss.render.MySurfaceView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/surfaceView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="2dp">
</com.example.tss.render.MySurfaceView>
- 创建
MulRenderAdapter
package com.example.tss.render;
import android.graphics.Bitmap;
import android.util.LruCache;
import android.view.LayoutInflater;
import android.view.SurfaceHolder;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.example.tss.databinding.ItemRenderPdfBinding;
/**
* @author Administrator
* @date 2024/3/29 0029 11:21
* @description: MulRenderAdapter
*/
public class MulRenderAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private LruCache<Integer, Bitmap> bitmapLruCache = new LruCache<>(Integer.MAX_VALUE);
public void add(Integer integer, Bitmap bitmap) {
this.bitmapLruCache.put(integer, bitmap);
notifyDataSetChanged();
}
public LruCache<Integer, Bitmap> getBitmapLruCache() {
return bitmapLruCache;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
@NonNull ItemRenderPdfBinding binding = ItemRenderPdfBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new MyHolder(binding);
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
MyHolder myHolder = (MyHolder) holder;
myHolder.binding.surfaceView.setOnAlreadyListener(new MySurfaceView.OnAlreadyListener() {
@Override
public Bitmap onAlready(SurfaceHolder holder) {
return bitmapLruCache.get(myHolder.getAdapterPosition());
}
@Override
public void onFinish(SurfaceHolder holder) {
}
});
}
@Override
public int getItemCount() {
return bitmapLruCache.putCount();
}
class MyHolder extends RecyclerView.ViewHolder {
ItemRenderPdfBinding binding;
public MyHolder(ItemRenderPdfBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
}
- 创建
fragment_pdf_renderer.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".PdfRendererFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycleView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
- 创建
PdfRendererFragment
package com.example.tss;
import android.content.Context;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.pdf.PdfRenderer;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.LinearSnapHelper;
import com.example.tss.databinding.FragmentPdfRendererBinding;
import com.example.tss.render.MulRenderAdapter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* A simple {@link Fragment} subclass.
* Use the {@link PdfRendererFragment#newInstance} factory method to
* create an instance of this fragment.
*/
public class PdfRendererFragment extends Fragment {
//来自assets根目录
private static final String PDF_FILE = "examplex.pdf";
private PdfRenderer renderer;
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private static final String ARG_PARAM1 = "param1";
private static final String ARG_PARAM2 = "param2";
// TODO: Rename and change types of parameters
private String mParam1;
private String mParam2;
private FragmentPdfRendererBinding binding;
private MulRenderAdapter mulRenderAdapter;
public PdfRendererFragment() {
// Required empty public constructor
}
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @param param1 Parameter 1.
* @param param2 Parameter 2.
* @return A new instance of fragment PdfRendererFragment.
*/
// TODO: Rename and change types and number of parameters
public static PdfRendererFragment newInstance(String param1, String param2) {
PdfRendererFragment fragment = new PdfRendererFragment();
Bundle args = new Bundle();
args.putString(ARG_PARAM1, param1);
args.putString(ARG_PARAM2, param2);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mParam1 = getArguments().getString(ARG_PARAM1);
mParam2 = getArguments().getString(ARG_PARAM2);
}
// 从 assets 中加载 PDF 文件
Context context = getActivity();
try {
AssetManager assetManager = context.getAssets();
InputStream inputStream = assetManager.open(PDF_FILE);
// assets中文件提取到File对象中
File file = new File(context.getFilesDir(), PDF_FILE);
FileOutputStream fileOutputStream = new FileOutputStream(file);
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) > 0) {
fileOutputStream.write(buffer, 0, length);
}
ParcelFileDescriptor parcelFileDescriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
renderer = new PdfRenderer(parcelFileDescriptor);
fileOutputStream.close();
inputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment R.layout.fragment_pdf_renderer
binding = FragmentPdfRendererBinding.inflate(inflater, container, false);
binding.recycleView.setLayoutManager(new LinearLayoutManager(getContext()));
mulRenderAdapter = new MulRenderAdapter();
binding.recycleView.setAdapter(mulRenderAdapter);
LinearSnapHelper snapHelper=new LinearSnapHelper();
snapHelper.attachToRecyclerView(binding.recycleView);
renderPage();
return binding.getRoot();
}
@Override
public void onDestroyView() {
if (mulRenderAdapter.getBitmapLruCache()!=null) {
for (int i = 0; i < mulRenderAdapter.getBitmapLruCache().putCount(); i++) {
mulRenderAdapter.getBitmapLruCache().get(i).recycle();
}
}
super.onDestroyView();
}
/**
* 渲染多个PDF页面到Bitmap,加载到SurfaceView
*/
private void renderPage() {
int w = getContext().getResources().getDisplayMetrics().widthPixels;
int padding= (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,100,getResources().getDisplayMetrics());
int h = getContext().getResources().getDisplayMetrics().heightPixels-padding;
// let us just render all pages
final int pageCount = renderer.getPageCount();
for (int i = 0; i < pageCount; i++) {
PdfRenderer.Page page = renderer.openPage(i);
Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
// say we render for showing on the screen
page.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY);
// do stuff with the bitmap
mulRenderAdapter.add(i, bitmap);
// close the page
page.close();
}
// close the renderer
renderer.close();
}
}
-
效果展示
-
PdfRenderer已移至一个模块,此模块可使用与平台版本无关的 Google Play 系统更新来进行更新,并且我们将通过创建兼容的 API Surface 低于 Android 15 的版本(称为PdfRendererPreV)来支持在 Android 11(API 级别 30)中更新这些变更。 -
生成查看
PdfDocument -
另一种思路直接绘制一个大长图渲染多个Bitmap,具备上下滑动效果