/**

  • 加载PDF文件
    */
    private void loadPdf() {
    Intent intent = getIntent();
    if (intent != null) {
    assetsFileName = intent.getStringExtra(“AssetsPdf”);
    if (assetsFileName != null) {
    displayFromAssets(assetsFileName);
    } else {
    uri = intent.getData();
    if (uri != null) {
    displayFromUri(uri);
    }
    }
    }
    }

/**

  • 基于assets显示 PDF 文件
  • @param fileName 文件名称
    */
    private void displayFromAssets(String fileName) {
    pdfView.fromAsset(fileName)
    .defaultPage(pageNumber)
    .onPageChange(this)
    .enableAnnotationRendering(true)
    .onLoad(this)
    .scrollHandle(new DefaultScrollHandle(this))
    .spacing(10) // 单位 dp
    .onPageError(this)
    .pageFitPolicy(FitPolicy.BOTH)
    .load();
    }

/**

  • 基于uri显示 PDF 文件
  • @param uri 文件路径
    */
    private void displayFromUri(Uri uri) {
    pdfView.fromUri(uri)
    .defaultPage(pageNumber)
    .onPageChange(this)
    .enableAnnotationRendering(true)
    .onLoad(this)
    .scrollHandle(new DefaultScrollHandle(this))
    .spacing(10) // 单位 dp
    .onPageError(this)
    .load();
    }

/**

  • 当成功加载PDF:
  • 1、可获取PDF的目录信息
  • @param nbPages the number of pages in this PDF file
    */
    @Override
    public void loadComplete(int nbPages) {
    //获得文档书签信息
    List<PdfDocument.Bookmark> bookmarks = pdfView.getTableOfContents();
    if (catelogues != null) {
    catelogues.clear();
    } else {
    catelogues = new ArrayList<>();
    }
    //将bookmark转为目录数据集合
    bookmarkToCatelogues(catelogues, bookmarks, 1);
    }

/**

  • 将bookmark转为目录数据集合(递归)
  • @param catelogues 目录数据集合
  • @param bookmarks 书签数据
  • @param level 目录树级别(用于控制树节点位置偏移)
    */
    private void bookmarkToCatelogues(List catelogues, List<PdfDocument.Bookmark> bookmarks, int level) {
    for (PdfDocument.Bookmark bookmark : bookmarks) {
    TreeNodeData nodeData = new TreeNodeData();
    nodeData.setName(bookmark.getTitle());
    nodeData.setPageNum((int) bookmark.getPageIdx());
    nodeData.setTreeLevel(level);
    nodeData.setExpanded(false);
    catelogues.add(nodeData);
    if (bookmark.getChildren() != null && bookmark.getChildren().size() > 0) {
    List treeNodeDatas = new ArrayList<>();
    nodeData.setSubset(treeNodeDatas);
    bookmarkToCatelogues(treeNodeDatas, bookmark.getChildren(), level + 1);
    }
    }
    }

@Override
public void onPageChanged(int page, int pageCount) {
pageNumber = page;
}

@Override
public void onPageError(int page, Throwable t) {
}

/**

  • 从缩略图、目录页面带回页码,跳转到指定PDF页面
  • @param requestCode
  • @param resultCode
  • @param data
    */
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (resultCode == RESULT_OK) {
    int pageNum = data.getIntExtra(“pageNum”, 0);
    if (pageNum > 0) {
    pdfView.jumpTo(pageNum);
    }
    }
    }

@Override
protected void onDestroy() {
super.onDestroy();
//是否内存
if (pdfView != null) {
pdfView.recycle();
}
}
}

PDF阅读页面的布局文件:activity_pdf.xml

<?xml version="1.0" encoding="utf-8"?>

<com.github.barteksc.pdfviewer.PDFView
android:id="@+id/pdfView"
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:layout_below="@+id/rl_top"/>

7、PDF目录树的实现

目录树的数据(目录名称、页码…),已在上个页面获取了,所以此页面只需考虑目录树控件的实现。

注意:之所以没在这个页面单独获取目录树的数据,主要考虑到android-pdfview、pdfium内存占用太大了,不想再次创建Pdf的相关对象。

7.1、PDF目录树效果图

7.2、树形控件如何实现?

安卓默认没有树形控件,不过我们可以使用RecyclerView或ListView实现。
如上图所示:

列表每一行为一条目录数据,主要包括:名称、页码;
如果有子目录,则出现箭头图片,该项可折叠、展开,箭头方向随之改变;
子目录的名称文本随目录树级别递增向右偏移;

当前Demo实现方式为RecyclerView,应该如何实现上面的效果?
可在adapter中处理页面效果、事件效果:
1、列表项内容展示

1、使用垂直线性布局管理器;
2、每个item包含:箭头图片(如果有子目录,则显示)、命令名称文本、页码文本;

2、折叠效果

1、控制adapter数据集合的内容即可,如果某节点折叠了,就把对应的子目录数据删除即可,
反之,加上,再notifyDataSetChanged通知数据源改变;
2、除此之外,还需有一个状态来标记当前节点是展开还是折叠,用于控制箭头图片方向的显示;

3、目录文本向右偏移效果

可通过目录树层级 * 固定左侧间隔(如: 20dp),然后为目录的textview控件设置偏移即可;

目录树层级树如何获取? 可选方案:
1、递归集合自动获取(需要遍历,效率低一点,如果是可编辑的目录结构,建议选择)
2、创建数据的时候,直接写死(因当前demo的PDF目录结构不会被编辑,所以直接选择这个方案吧)

7.3、代码实现:

树形控件的数据对象TreeNodeData:

/**

  • 树形控件数据类(会用于页面间传输,所以需实现Serializable 或 Parcelable)
  • 作者:齐行超
  • 日期:2019.08.07
    */
    public class TreeNodeData implements Serializable {
    //名称
    private String name;
    //页码
    private int pageNum;
    //是否已展开(用于控制树形节点图片显示,即箭头朝向图片)
    private boolean isExpanded;
    //展示级别(1级、2级…,用于控制树形节点缩进位置)
    private int treeLevel;
    //子集(用于加载子节点,也用于判断是否显示箭头图片,如集合不为空,则显示)
    private List subset;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getPageNum() {
return pageNum;
}

public void setPageNum(int pageNum) {
this.pageNum = pageNum;
}

public boolean isExpanded() {
return isExpanded;
}

public void setExpanded(boolean expanded) {
isExpanded = expanded;
}

public int getTreeLevel() {
return treeLevel;
}

public void setTreeLevel(int treeLevel) {
this.treeLevel = treeLevel;
}

public List getSubset() {
return subset;
}

public void setSubset(List subset) {
this.subset = subset;
}
}

树形控件适配器 : TreeAdapter

/**

  • 树形控件适配器
  • 作者:齐行超
  • 日期:2019.08.07
    */
    public class TreeAdapter extends RecyclerView.Adapter<TreeAdapter.TreeNodeViewHolder> {
    //上下文
    private Context context;
    //数据
    public List data;
    //展示数据(由层级结构改为平面结构)
    public List displayData;
    //treelevel间隔(dp)
    private int maginLeft;
    //委托对象
    private TreeEvent delegate;

/**

  • 构造函数
  • @param context 上下文
  • @param data 数据
    */
    public TreeAdapter(Context context, List data) {
    this.context = context;
    this.data = data;
    maginLeft = UIUtils.dip2px(context, 20);
    displayData = new ArrayList<>();

//数据转为展示数据
dataToDiaplayData(data);
}

/**

  • 数据转为展示数据
  • @param data 数据
    */
    private void dataToDiaplayData(List data) {
    for (TreeNodeData nodeData : data) {
    displayData.add(nodeData);
    if (nodeData.isExpanded() && nodeData.getSubset() != null) {
    dataToDiaplayData(nodeData.getSubset());
    }
    }
    }

/**

  • 数据集合转为可显示的集合
    */
    private void reDataToDiaplayData() {
    if (this.data == null || this.data.size() == 0) {
    return;
    }
    if(displayData == null){
    displayData = new ArrayList<>();
    }else{
    displayData.clear();
    }
    dataToDiaplayData(this.data);
    notifyDataSetChanged();
    }

@Override
public TreeNodeViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.tree_item, null);
return new TreeNodeViewHolder(view);
}

@Override
public void onBindViewHolder(TreeNodeViewHolder holder, int position) {
final TreeNodeData data = displayData.get(position);
//设置图片
if (data.getSubset() != null) {
holder.img.setVisibility(View.VISIBLE);
if (data.isExpanded()) {
holder.img.setImageResource(R.drawable.arrow_h);
} else {
holder.img.setImageResource(R.drawable.arrow_v);
}
} else {
holder.img.setVisibility(View.INVISIBLE);
}
//设置图片偏移位置
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) holder.img.getLayoutParams();
int ratio = data.getTreeLevel() <= 0? 0 : data.getTreeLevel()-1;
params.setMargins(maginLeft * ratio, 0, 0, 0);
holder.img.setLayoutParams(params);

//显示文本
holder.title.setText(data.getName());
holder.pageNum.setText(String.valueOf(data.getPageNum()));

//图片点击事件
holder.img.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//控制树节点展开、折叠
data.setExpanded(!data.isExpanded());
//刷新数据源
reDataToDiaplayData();
}
});
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//回调结果
if(delegate!=null){
delegate.onSelectTreeNode(data);
}
}
});
}

@Override
public int getItemCount() {
return displayData.size();
}

/**

  • 定义RecyclerView的ViewHolder对象
    */
    class TreeNodeViewHolder extends RecyclerView.ViewHolder {
    ImageView img;
    TextView title;
    TextView pageNum;

public TreeNodeViewHolder(View view) {
super(view);
img = view.findViewById(R.id.iv_arrow);
title = view.findViewById(R.id.tv_title);
pageNum = view.findViewById(R.id.tv_pagenum);
}
}

/**

  • 接口:Tree事件
    /
    public interface TreeEvent{
    /
    *
  • 当选择了某tree节点
  • @param data tree节点数据
    */
    void onSelectTreeNode(TreeNodeData data);
    }

/**

  • 设置Tree的事件
  • @param treeEvent Tree的事件对象
    */
    public void setTreeEvent(TreeEvent treeEvent){
    this.delegate = treeEvent;
    }
    }

PDF目录树页面:PDFCatelogueActivity

/**

  • UI页面:PDF目录
  • 1、用于显示Pdf目录信息
  • 2、点击tree item,带回Pdf页码到前一个页面
  • 作者:齐行超
  • 日期:2019.08.07
    */
    public class PDFCatelogueActivity extends AppCompatActivity implements TreeAdapter.TreeEvent {

RecyclerView recyclerView;
Button btn_back;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
UIUtils.initWindowStyle(getWindow(), getSupportActionBar());
setContentView(R.layout.activity_catelogue);

initView();//初始化控件
setEvent();//设置事件
loadData();//加载数据
}

/**

  • 初始化控件
    */
    private void initView() {
    btn_back = findViewById(R.id.btn_back);
    recyclerView = findViewById(R.id.rv_tree);
    }

/**

  • 设置事件
    */
    private void setEvent() {
    btn_back.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
    PDFCatelogueActivity.this.finish();
    }
    });
    }

/**

  • 加载数据
    */
    private void loadData() {
    //从intent中获得传递的数据
    Intent intent = getIntent();
    List catelogues = (List) intent.getSerializableExtra(“catelogues”);

//使用RecyclerView加载数据
LinearLayoutManager llm = new LinearLayoutManager(this);
llm.setOrientation(LinearLayoutManager.VERTICAL);
recyclerView.setLayoutManager(llm);
TreeAdapter adapter = new TreeAdapter(this, catelogues);
adapter.setTreeEvent(this);
recyclerView.setAdapter(adapter);
}

/**

  • 点击tree item,带回Pdf页码到前一个页面
  • @param data tree节点数据
    */
    @Override
    public void onSelectTreeNode(TreeNodeData data) {
    Intent intent = new Intent();
    intent.putExtra(“pageNum”, data.getPageNum());
    setResult(Activity.RESULT_OK, intent);
    finish();
    }
    }

PDF目录树的布局文件:activity_catelogue.xml

<?xml version="1.0" encoding="utf-8"?>

<android.support.v7.widget.RecyclerView
android:id="@+id/rv_tree"
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:layout_below="@+id/rl_top" />

8、PDF预览缩略图

这个功能算是本Demo中最为复杂的一个了:

如何将PDF某页面的内容转成图片?(默认是无法从pdfview中获得页面图片的)
如何减少图片内存的占用?(用户可能快速滑动列表,实时读取、显示多张图片)
如何优化PDF预览缩略图列表的滑动体验?(图片的获取需要一定时间)
如何合理的及时释放内存占用?

8.1、PDF预览缩略图列表的效果图

8.2、功能分析

1、如何将PDF某页面的内容转成图片?

查看android-pdfview的源码,无法通过PDFView控件获得某页面的图片,所以只能分析pdfium sdk的A

《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》浏览器打开:qq.cn.hn/FTe 开源分享

PI了,如下图:

pdfium的renderPageBitmap方法可以将页面渲染成图片,不过需要传递一系列参数,而且要小心OutOfMemoryError。

那么,我们需要在代码中获取或者创建PdfiumCore对象,调用该方法,传递PdfDocument等参数,当bitmap使用完后,应及时释放掉。

2、如何减少内存的占用?

内存主要包括:
1、pdfium sdk加载pdf文件产生的内存(我们无法优化)
2、android-pdfview产生的内存(如果有需要,可改其源码)
3、我们将pdf页面转为缩略图,而产生的内存(必须优化,否则,容易oom)

3.1、当PdfiumCore、PdfDocument不再使用时,应及时关闭;
3.2、当缩略图不再使用时,应及时释放;
3.3、可使用LruCache临时缓存缩略图,防止重复调用renderPageBitmap获取图片;
3.4、LruCache应合理管控,当预览页面关闭时,必须清空缓存,以释放内存;
3.5、创建图片时,应使用RGB_565,能节约内存开销(一个像素点,占2字节)
3.6、创建图片时,应尽可能小的指定图片的宽高,能看清就行(图片占用的内存 = 宽 * 高 * 一个像素点占的字节数)

3、如何优化PDF预览缩略图列表的滑动体验?

查看pdfium源码,调用renderPageBitmap方法之前,还必须确保对应的页面已被打开,即调用了openPage方法。然而,这两个方法都需要一定时间才能执行完成的。

那么,如果我们直接在主线程中让每个RecylerVew的item分别调用renderPageBitmap方法,滑动列表时,会感觉特别卡,所以该方法只能放在子线程中调用了。

那么问题又来了,那么多子线程应该如何管控?

1、考虑CPU的占用,应使用线程池控制子线程并发、阻塞;
2、考虑到用户滑动速度,有可能某线程正执行或者阻塞着呢,页面已经滑过去了,那么,即使该线程加载出来了图片,也无法显示到列表中。所以对于RecyclerView已不可见的Item项对应的线程,应及时取消,防止做无用功,也节省了内存和cpu开销。

8.3、功能实现

预览缩略图工具类:PreviewUtils

/**

  • 预览缩略图工具类
  • 1、pdf页面转为缩略图
  • 2、图片缓存管理(仅保存到内存,可使用LruCache,注意空间大小控制)
  • 3、多线程管理(线程并发、阻塞、Future任务取消)
  • 作者:齐行超
  • 日期:2019.08.08
    */
    public class PreviewUtils {
    //图片缓存管理
    private ImageCache imageCache;
    //单例
    private static PreviewUtils instance;
    //线程池
    ExecutorService executorService;
    //线程任务集合(可用于取消任务)
    HashMap<String, Future> tasks;

/**

  • 单例(仅主线程调用,无需做成线程安全的)
  • @return PreviewUtils实例对象
    */
    public static PreviewUtils getInstance() {
    if (instance == null) {
    instance = new PreviewUtils();
    }
    return instance;
    }

/**

  • 默认构造函数
    */
    private PreviewUtils() {
    //初始化图片缓存管理对象
    imageCache = new ImageCache();
    //创建并发线程池(建议最大并发数大于1屏grid item的数量)
    executorService = Executors.newFixedThreadPool(20);
    //创建线程任务集合,用于取消线程执行
    tasks = new HashMap<>();
    }

/**

  • 从pdf文件中加载图片
  • @param context 上下文
  • @param imageView 图片控件
  • @param pdfiumCore pdf核心对象
  • @param pdfDocument pdf文档对象
  • @param pdfName pdf文件名称
  • @param pageNum pdf页码
    */
    public void loadBitmapFromPdf(final Context context,
    final ImageView imageView,
    final PdfiumCore pdfiumCore,
    final PdfDocument pdfDocument,
    final String pdfName,
    final int pageNum) {
    //判断参数合法性
    if (imageView == null || pdfiumCore == null || pdfDocument == null || pageNum < 0) {
    return;
    }

try {
//缓存key
final String keyPage = pdfName + pageNum;

//为图片控件设置标记
imageView.setTag(keyPage);

Log.i(“PreViewUtils”, “加载pdf缩略图:” + keyPage);

//获得imageview的尺寸(注意:如果使用正常控件尺寸,太占内存了)
/int w = imageView.getMeasuredWidth();
int h = imageView.getMeasuredHeight();
final int reqWidth = w == 0 ? UIUtils.dip2px(context,100) : w;
final int reqHeight = h == 0 ? UIUtils.dip2px(context,150) : h;
/

//内存大小= 图片宽度 * 图片高度 * 一个像素占的字节数(RGB_565 所占字节:2)
//注意:如果使用正常控件尺寸,太占内存了,所以此处指定四缩略图看着会模糊一点
final int reqWidth = 100;
final int reqHeight = 150;

//从缓存中取图片
Bitmap bitmap = imageCache.getBitmapFromLruCache(keyPage);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
return;
}

//使用线程池管理子线程
Future future = executorService.submit(new Runnable() {
@Override
public void run() {
//打开页面(调用renderPageBitmap方法之前,必须确保页面已open,重要)
pdfiumCore.openPage(pdfDocument, pageNum);

//调用native方法,将Pdf页面渲染成图片
final Bitmap bm = Bitmap.createBitmap(reqWidth, reqHeight, Bitmap.Config.RGB_565);
pdfiumCore.renderPageBitmap(pdfDocument, bm, pageNum, 0, 0, reqWidth, reqHeight);

//切回主线程,设置图片
if (bm != null) {
//将图片加入缓存
imageCache.addBitmapToLruCache(keyPage, bm);

//切回主线程加载图片
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
if (imageView.getTag().toString().equals(keyPage)) {
imageView.setImageBitmap(bm);
Log.i(“PreViewUtils”, “加载pdf缩略图:” + keyPage + “…已设置!!”);
}
}
});
}
}
});

//将任务添加到集合
tasks.put(keyPage, future);
} catch (Exception ex) {
ex.printStackTrace();
}
}

/**

  • 取消从pdf文件中加载图片的任务
  • @param keyPage 页码
    */
    public void cancelLoadBitmapFromPdf(String keyPage) {
    if (keyPage == null || !tasks.containsKey(keyPage)) {
    return;
    }
    try {
    Log.i(“PreViewUtils”, “取消加载pdf缩略图:” + keyPage);
    Future future = tasks.get(keyPage);
    if (future != null) {
    future.cancel(true);
    Log.i(“PreViewUtils”, “取消加载pdf缩略图:” + keyPage + “…已取消!!”);
    }
    } catch (Exception ex) {
    ex.printStackTrace();
    }
    }

/**

  • 获得图片缓存对象
  • @return 图片缓存
    */
    public ImageCache getImageCache(){
    return imageCache;
    }

/**

  • 图片缓存管理
    */
    public class ImageCache {
    //图片缓存
    private LruCache<String, Bitmap> lruCache;

//构造函数
public ImageCache() {
//初始化 lruCache
//int maxMemory = (int) Runtime.getRuntime().maxMemory();
//int cacheSize = maxMemory/8;
int cacheSize = 1024 * 1024 * 30;//暂时设定30M
lruCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight();
}
};
}

/**

  • 从缓存中取图片
  • @param key 键
  • @return 图片
    */
    public synchronized Bitmap getBitmapFromLruCache(String key) {
    if(lruCache!= null) {
    return lruCache.get(key);
    }
    return null;
    }

/**

  • 向缓存中加图片
  • @param key 键
  • @param bitmap 图片
    */
    public synchronized void addBitmapToLruCache(String key, Bitmap bitmap) {
    if (getBitmapFromLruCache(key) == null) {
    if (lruCache!= null && bitmap != null)
    lruCache.put(key, bitmap);
    }
    }

/**

  • 清空缓存
    */
    public void clearCache(){
    if(lruCache!= null){
    lruCache.evictAll();
    }
    }
    }
    }

grid列表适配器: GridAdapter

/**

  • grid列表适配器
  • 作者:齐行超
  • 日期:2019.08.08
    */
    public class GridAdapter extends RecyclerView.Adapter<GridAdapter.GridViewHolder> {

Context context;
PdfiumCore pdfiumCore;
PdfDocument pdfDocument;
String pdfName;
int totalPageNum;

public GridAdapter(Context context, PdfiumCore pdfiumCore, PdfDocument pdfDocument, String pdfName, int totalPageNum) {
this.context = context;
this.pdfiumCore = pdfiumCore;
this.pdfDocument = pdfDocument;
this.pdfName = pdfName;
this.totalPageNum = totalPageNum;
}

@Override
public GridViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.grid_item, null);
return new GridViewHolder(view);
}

@Override
public void onBindViewHolder(GridViewHolder holder, int position) {
//设置PDF图片
final int pageNum = position;
PreviewUtils.getInstance().loadBitmapFromPdf(context, holder.iv_page, pdfiumCore, pdfDocument, pdfName, pageNum);
//设置PDF页码
holder.tv_pagenum.setText(String.valueOf(position));
//设置Grid事件
holder.iv_page.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(delegate!=null){
delegate.onGridItemClick(pageNum);
}
}
});
return;
}

@Override
public void onViewDetachedFromWindow(GridViewHolder holder) {
super.onViewDetachedFromWindow(holder);
try {
//item不可见时,取消任务
if(holder.iv_page!=null){
PreviewUtils.getInstance().cancelLoadBitmapFromPdf(holder.iv_page.getTag().toString());
}

//item不可见时,释放bitmap (注意:本Demo使用了LruCache缓存来管理图片,此处可注释掉)
/Drawable drawable = holder.iv_page.getDrawable();
if (drawable != null) {
Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
if (bitmap != null && !bitmap.isRecycled()) {
bitmap.recycle();
bitmap = null;
Log.i(“PreViewUtils”,“销毁pdf缩略图:”+holder.iv_page.getTag().toString());
}
}
/
}catch (Exception ex){
ex.printStackTrace();
}
}

@Override
public int getItemCount() {
return totalPageNum;
}

class GridViewHolder extends RecyclerView.ViewHolder {
ImageView iv_page;
TextView tv_pagenum;

public GridViewHolder(View itemView) {
super(itemView);
iv_page = itemView.findViewById(R.id.iv_page);
tv_pagenum = itemView.findViewById(R.id.tv_pagenum);
}
}

/**

  • 接口:Grid事件
    /
    public interface GridEvent{
    /
    *
  • 当选择了某Grid项
  • @param position tree节点数据
    */
    void onGridItemClick(int position);
    }

/**

  • 设置Grid事件
  • @param event Grid事件对象
    */
    public void setGridEvent(GridEvent event){
    this.delegate = event;
    }

//Grid事件委托
private GridEvent delegate;
}

PDF预览缩略图页面:PDFPreviewActivity

/**

  • UI页面:PDF预览缩略图(注意:此页面,需多关注内存管控)
  • 1、用于显示Pdf缩略图信息
  • 2、点击缩略图,带回Pdf页码到前一个页面
  • 作者:齐行超
    != null && !bitmap.isRecycled()) {
    bitmap.recycle();
    bitmap = null;
    Log.i(“PreViewUtils”,“销毁pdf缩略图:”+holder.iv_page.getTag().toString());
    }
    }*/
    }catch (Exception ex){
    ex.printStackTrace();
    }
    }

@Override
public int getItemCount() {
return totalPageNum;
}

class GridViewHolder extends RecyclerView.ViewHolder {
ImageView iv_page;
TextView tv_pagenum;

public GridViewHolder(View itemView) {
super(itemView);
iv_page = itemView.findViewById(R.id.iv_page);
tv_pagenum = itemView.findViewById(R.id.tv_pagenum);
}
}

/**

  • 接口:Grid事件
    /
    public interface GridEvent{
    /
    *
  • 当选择了某Grid项
  • @param position tree节点数据
    */
    void onGridItemClick(int position);
    }

/**

  • 设置Grid事件
  • @param event Grid事件对象
    */
    public void setGridEvent(GridEvent event){
    this.delegate = event;
    }

//Grid事件委托
private GridEvent delegate;
}

PDF预览缩略图页面:PDFPreviewActivity

/**

  • UI页面:PDF预览缩略图(注意:此页面,需多关注内存管控)
  • 1、用于显示Pdf缩略图信息
  • 2、点击缩略图,带回Pdf页码到前一个页面
  • 作者:齐行超

Android原生PDF功能实现,Android开发面试书籍相关推荐

  1. Android原生计步功能的实现,记录当日步数(仿微信运动),不需要后台service

    Android原生计步功能的实现,记录当日步数(仿微信运动),不需要后台service 概述:通过调用Android4.4以上系统自带的计步传感器Sensor.TYPE_STEP_COUNTER,实现 ...

  2. Android Studio多功能闹钟[android源码]

    Android Studio多功能闹钟[android源码] 多功能闹钟App 简 介 主 要 技 术 界面截图 主要代码 源码链接 作者信息 多功能闹钟App 简 介 此多功能闹钟界面采用TabHo ...

  3. 安卓开发面试书籍,全世界都在问Android开发凉了吗?建议收藏

    前言 本想今年辞掉工作大干一场,没想到碰到疫情,家里蹲了3个月-,还好字节能给一次机会.前阵子字节跳动的提前批开始了,看宣传是说有海量HC,机会多多,本着涨涨面经的心理,然后就投递了一下杭州那边的部门 ...

  4. android原生分享功能,Android原生分享到微博、微信等平台的实现方式

    在这个版本功能涉及到Android分享方式的变更,需求要求: 分享只支持新浪微博.微信,其他方式均去掉. 为了更好的测试还是要看下Android分享实现分享的方式,然后才能更好地评估测试时间和设计测试 ...

  5. android原生代码转h5,Android原生和H5交互;Android和H5混合开发;WebView点击H5界面跳转到Android原生界面。...

    当时业务的需求是这样的,H5有一个活动商品列表的界面,IOS和Android共用这一个界面,点击商品可以跳转到Android原生的商品详情界面并传递商品ID:  大概就是点击H5界面跳转到Androi ...

  6. android webview缩放功能,在Android WebView中启用/禁用缩放

    千万里不及你 在为客户开发Android应用程序时,我们遇到了同样的问题,我设法绕过了这一限制.我查看了WebView类的Android源代码,发现了updateZoomButtonsEnabled( ...

  7. android实现下载功能实现,Android开发之DownloadManager的使用

    Android 开发中,经常有从服务器下载数据的需求出现,尤其是在线更新App的情形.其基本思路是根据本地的App版本号和服务器的版本号进行比较,如果服务器版本较新,再进行提示然后下载Apk最后进行安 ...

  8. android 收藏歌曲功能,基于android的网络音乐播放器-回调实现音乐播放及音乐收藏的实现(三)...

    作为android初学者,最近把疯狂android讲义和疯狂Java讲义看了一遍,看到书中介绍的知识点非常多,很难全部记住,为了更好的掌握基础知识点,我将开发一个网络音乐播放器-EasyMusic来巩 ...

  9. android客服功能介绍,Android 客服工作台 SDK

    Android 客服工作台 SDK 开发工具 目录 build.gradle 配置 添加依赖,在app的build.gradle 中dependencies中加入如下(添加后需同步gradle): a ...

最新文章

  1. Nginx + Spring Boot 实现负载均衡
  2. Python变量使用前必须先声明,并且一旦声明就不能在当前作用域内改变其类型————(错)
  3. go 使用sarama写入kafka数据时间戳问题
  4. spring指导的index.html在spring文件夹中的位置
  5. 网规:第4章 网络安全-4.5IDS和IPS
  6. Android4.4深入浅出之SurfaceFlinger总体结构
  7. CC2530 串口通信
  8. 数据结构中的时间复杂度的计算
  9. 如何注册海外邮箱?如何进行邮箱注册163,这些技巧交给你
  10. 微信签名错误解决步骤
  11. unity 3d原创制作射击游戏(全完整版+安卓apk编译)
  12. linux无人值守安装实验,无人值守批量安装linux操作系统
  13. dellr720服务器性能,戴尔服务器R720
  14. 网站服务器 64位,如何将win7系统从32位升级到64位_网站服务器运行维护,win7,32位,64位...
  15. 2023年浙江工业大学MPA提前批招生通知
  16. 剑指 Offer 31-40
  17. 自动化运维之自动化监控
  18. 云课堂缺勤补签软件_GO柱状图绘图指南 | 云课堂(22)
  19. Linux命令:netstat【监控TCP/IP网络,可以显示路由表、实际的网络连接以及每一个网络接口设备的状态信息】【TCP的11种状态】
  20. 如何在不泄露私人信息的情况下共享屏幕

热门文章

  1. 字典爆破php,【爆破大法】百站爆破经验总结(附带各种爆破字典)
  2. 一个年轻董事长给大学生18条很好的建议
  3. 每天8小时自学java_MessageResources
  4. Zookeeper-api基础教程
  5. wpf datagrid设置右键菜单打开时选中项的背景色
  6. kesioncms的部分用户,在后台基本信息设置里自动获取域名多出端口号的去除
  7. 9-斐波拉契数列解法归纳
  8. Hexo-显示用户头像
  9. 2023年4月的12篇AI论文推荐
  10. 股市资讯第二证券|畅通物流配送环节 保障群众生活所需